Refactored Glyph Textures into Atlases.

This commit is contained in:
Josh W 2023-11-26 19:30:04 -05:00
parent cac8c85fcb
commit c1515b2bb9
2 changed files with 124 additions and 101 deletions

View file

@ -142,4 +142,29 @@ impl EgaColors {
}; };
col 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
}
} }

View file

@ -4,17 +4,20 @@ use std::thread::sleep;
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
use bitvec::prelude::*; use bitvec::prelude::*;
use sdl2::Sdl;
use sdl2::event::Event; use sdl2::event::Event;
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use sdl2::pixels::{Color, PixelFormatEnum}; use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect; 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 cp437;
pub mod ega; pub mod ega;
use cp437::GLYPHS; use cp437::GLYPHS;
use ega::EgaColors; use ega::EgaColors;
const SCREEN_HEIGHT: u32 = 350; const SCREEN_HEIGHT: u32 = 350;
const SCREEN_WIDTH: u32 = 640; const SCREEN_WIDTH: u32 = 640;
@ -24,8 +27,11 @@ const GLYPH_ROWS: u8 = 25;
const GLYPH_HEIGHT: u8 = 14; const GLYPH_HEIGHT: u8 = 14;
const GLYPH_WIDTH: u8 = 8; 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 // 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 // Total screen blocks = GLYPH_COLUMNS * GLYPH_ROWS
const TOTAL_SCREEN_BLOCKS: usize = 2000; const TOTAL_SCREEN_BLOCKS: usize = 2000;
@ -46,73 +52,47 @@ impl ScreenBlock {
ScreenBlock { blink, background, foreground, glyph } ScreenBlock { blink, background, foreground, glyph }
} }
pub fn attrs_to_index(blink: bool, background: u8, foreground: u8, glyph: u8) -> usize { pub fn as_index(&self) -> usize {
let num = (usize::from(blink) << 15) + EgaColors::textattr_to_u8(false, self.background, self.foreground) as usize
(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 to_canvas( pub fn to_canvas(
&self, &self,
canvas: &mut WindowCanvas, canvas: &mut WindowCanvas,
textures: &Vec<Texture>, textures: &Vec<Texture>,
x: u8, x: i32,
y: u8, y: i32,
blink: &bool blink_state: &bool
) { ) {
let tex_idx = match blink { let tex_idx = match blink_state {
false => self.as_texture_index(), false => self.as_index(),
true => match self.blink { true => match self.blink {
false => self.as_texture_index(), false => self.as_index(),
true => ScreenBlock::new(self.blink, self.background, self.background, self.glyph).as_texture_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 // These next two assignments look odd mainly because of Rust's current
// issue with exclusive range patterns. See here: // issue with exclusive range patterns. See here:
// https://github.com/rust-lang/rust/issues/37854 // https://github.com/rust-lang/rust/issues/37854
let dest_x = match x { let dest_x = match x {
n if (0..=GLYPH_COLUMNS - 1).contains(&n) => GLYPH_WIDTH as i32 * x as i32, n if (0..=GLYPH_COLUMNS as i32 - 1).contains(&n) => GLYPH_WIDTH as i32 * x,
//0..=79 => GLYPH_WIDTH as i32 * x as i32,
_ => panic!("x is out of range! Must be 0..79 inclusive."), _ => panic!("x is out of range! Must be 0..79 inclusive."),
}; };
let dest_y = match y { let dest_y = match y {
n if (0..=GLYPH_ROWS - 1).contains(&n) => GLYPH_HEIGHT as i32 * y as i32, n if (0..=GLYPH_ROWS as i32 - 1).contains(&n) => GLYPH_HEIGHT as i32 * y,
//0..=24 => GLYPH_HEIGHT as i32 * y as i32,
_ => panic!("y is out of range! Must be 0..24 inclusive."), _ => panic!("y is out of range! Must be 0..24 inclusive."),
}; };
match canvas.copy( match canvas.copy(
&textures[tex_idx], &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)), Rect::new(dest_x, dest_y, u32::from(GLYPH_WIDTH), u32::from(GLYPH_HEIGHT)),
) { ) {
Ok(()) => (), Ok(()) => (),
@ -121,31 +101,7 @@ impl ScreenBlock {
} }
} }
pub fn update(elapsed: u128) { pub fn init_sdl() -> (Sdl, Window) {
//println!("Updated after {} nanoseconds.", elapsed);
}
pub fn render(canvas: &mut WindowCanvas, textures: &Vec<Texture>, screen: &[ScreenBlock], blink: &bool) {
canvas.set_draw_color::<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<u128, SystemTimeError> {
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 = match sdl2::init() { let sdl_context = match sdl2::init() {
Ok(sdl_context) => sdl_context, Ok(sdl_context) => sdl_context,
Err(err) => panic!("SDL could not initialize! SDL_Error: {}", err), Err(err) => panic!("SDL could not initialize! SDL_Error: {}", err),
@ -163,34 +119,34 @@ pub fn main() {
Ok(window) => window, Ok(window) => window,
Err(err) => panic!("SDL could not create a window! SDL_Error: {}", err), Err(err) => panic!("SDL could not create a window! SDL_Error: {}", err),
}; };
(sdl_context, window)
}
let mut canvas = match window.into_canvas().build() { pub fn init_glyph_atlases(creator: &TextureCreator<WindowContext>) -> Vec<Texture<'_>> {
Ok(canvas) => canvas, let mut atlases: Vec<Texture> = Vec::new();
Err(err) => panic!("SDL could not create a canvas! SDL_Error: {}", err), 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) {
let texture_creator = canvas.texture_creator();
let mut glyph_textures: Vec<Texture> = 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)) {
Ok(texture) => texture, Ok(texture) => texture,
Err(err) => panic!("SDL could not create texture. SDL Error: {}", err), Err(err) => panic!("SDL could not create texture. SDL Error: {}", err),
}; };
match texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { match texture.with_lock(None, |buffer: &mut [u8], pitch: usize| {
for (y, line) in GLYPHS[cp as usize].iter().enumerate() { for (glyph_num, glyph_data) in GLYPHS.iter().enumerate() {
let bits = line.view_bits::<Msb0>(); let glyph_x = glyph_num % 32;
for (x, pixel) in bits.iter().enumerate() { let glyph_y = glyph_num / 32;
let offset = y * pitch + x * 3; for (local_y, line) in glyph_data.iter().enumerate() {
if pixel == false { let bits = line.view_bits::<Msb0>();
buffer[offset] = EgaColors::u8_to_rgb(back).r; for (local_x, pixel) in bits.iter().enumerate() {
buffer[offset + 1] = EgaColors::u8_to_rgb(back).g; let offset = ((glyph_y * GLYPH_HEIGHT as usize * pitch) + (local_y * pitch)) + ((glyph_x * GLYPH_WIDTH as usize) + local_x) * 3;
buffer[offset + 2] = EgaColors::u8_to_rgb(back).b; if pixel == false {
} else { buffer[offset] = EgaColors::u8_to_rgb(back).r;
buffer[offset] = EgaColors::u8_to_rgb(fore).r; buffer[offset + 1] = EgaColors::u8_to_rgb(back).g;
buffer[offset + 1] = EgaColors::u8_to_rgb(fore).g; buffer[offset + 2] = EgaColors::u8_to_rgb(back).b;
buffer[offset + 2] = EgaColors::u8_to_rgb(fore).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(()) => (), Ok(()) => (),
Err(err) => panic!("Unable to modify texture. SDL Error: {}", err), 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 { 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 { for row in 1..GLYPH_ROWS-1 {
let idx = row as usize * GLYPH_COLUMNS as usize; let idx = row as usize * GLYPH_COLUMNS 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[idx + 59] = 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 { for col in 0..60 {
let idx = (GLYPH_ROWS - 1) as usize * GLYPH_COLUMNS as usize + col as usize; 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<Texture>, screen: &[ScreenBlock], blink: &bool) {
canvas.set_draw_color::<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<u128, SystemTimeError> {
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<Texture> = 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_tick: u128 = 0;
let mut blink_state: bool = false; 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() { let mut event_pump = match sdl_context.event_pump() {
Ok(event_pump) => event_pump, Ok(event_pump) => event_pump,
@ -259,7 +257,7 @@ pub fn main() {
blink_tick += elapsed; 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)); //sleep(Duration::new(0, FPS_60_HERTZ));
prev_tick = curr_tick; prev_tick = curr_tick;