summaryrefslogtreecommitdiff
path: root/src/devices/screen.rs
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-04-16 10:51:13 +1200
committerBen Bridle <bridle.benjamin@gmail.com>2024-04-16 10:51:26 +1200
commit6b3796c9a0d3a2f1422bcbde4790c43417659722 (patch)
tree6429a5fa2f8c4d3b26790775e07e46e6338b61d3 /src/devices/screen.rs
parent28101de56231252ca0cfa6a9f107b75112c9acad (diff)
downloadbedrock-pc-6b3796c9a0d3a2f1422bcbde4790c43417659722.zip
Update devices to match new specifications
Diffstat (limited to 'src/devices/screen.rs')
-rw-r--r--src/devices/screen.rs344
1 files changed, 67 insertions, 277 deletions
diff --git a/src/devices/screen.rs b/src/devices/screen.rs
index c96d1c4..4394b6c 100644
--- a/src/devices/screen.rs
+++ b/src/devices/screen.rs
@@ -1,37 +1,50 @@
mod sprite_data;
-mod vector_points;
+mod draw_line;
+mod draw_rect;
+mod draw_sprite;
pub use sprite_data::*;
-pub use vector_points::*;
-
use geometry::HasDimensions;
use phosphor::*;
-
use std::cmp::{min, max, Ordering};
use std::iter::zip;
pub type ScreenDimensions = geometry::Dimensions<u16>;
pub type ScreenPosition = geometry::Point<u16>;
+pub type Plane = [u8; 8];
+pub type Sprite = [[u8; 8]; 8];
+
+const TRANSPARENT: u8 = 0x08;
+const FLIP_DIAGONAL: u8 = 0x04;
+const FLIP_VERTICAL: u8 = 0x02;
+const FLIP_HORIZONTAL: u8 = 0x01;
+const NEGATIVE: u8 = 0x80;
+const VERTICAL: u8 = 0x40;
+
#[derive(Copy, Clone)]
pub enum ScreenLayer { Background, Foreground }
+macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; }
+
+
pub struct ScreenDevice {
pub wake_flag: bool,
- /// Each byte represents a screen pixel, left-right-top-bottom.
+
+ /// Each byte represents a screen pixel, left-to-right and top-to-bottom.
// Only the bottom four bits of each byte are used.
pub foreground: Vec<u8>,
pub background: Vec<u8>,
- pub is_dirty: bool,
- pub is_resizable: bool,
+ pub dirty: bool,
+ pub resizable: bool,
pub cursor: ScreenPosition,
+ pub vector: ScreenPosition,
pub dimensions: ScreenDimensions,
- pub sprite_data: SpriteData,
- pub palette: [Colour; 16],
+
pub palette_high: u8,
- pub sprite_colours: [u8; 4],
- pub vector: VectorPoints,
+ pub palette: [Colour; 16],
+ pub sprite_buffer: SpriteBuffer,
}
impl ScreenDevice {
@@ -41,25 +54,25 @@ impl ScreenDevice {
foreground: Vec::new(),
background: Vec::new(),
- is_dirty: false,
- is_resizable: true,
+ dirty: false,
+ resizable: true,
cursor: ScreenPosition::ZERO,
+ vector: ScreenPosition::ZERO,
dimensions: ScreenDimensions::ZERO,
- sprite_data: SpriteData::new(),
- palette: [Colour::BLACK; 16],
- palette_high: 0,
- sprite_colours: [0; 4],
- vector: VectorPoints::new(),
+ palette_high: 0,
+ palette: [Colour::BLACK; 16],
+ sprite_buffer: SpriteBuffer::new(),
}
}
- pub fn set_size(&mut self, dimensions: ScreenDimensions) {
- self.is_resizable = false;
- self.resize(dimensions);
+ pub fn set_size(&mut self) {
+ self.resizable = false;
+ self.resize(self.dimensions);
}
+ // Resize the screen buffers while preserving the current content.
pub fn resize(&mut self, dimensions: ScreenDimensions) {
let old_width = self.dimensions.width as usize;
let old_height = self.dimensions.height as usize;
@@ -100,19 +113,20 @@ impl ScreenDevice {
};
self.dimensions = dimensions;
- self.is_dirty = true;
+ self.dirty = true;
self.wake_flag = true;
}
pub fn render(&mut self, buffer: &mut Buffer) {
// Pre-calculate a lookup table for the colour palette
- let mut palette = [Colour::BLACK; 256];
- for (i, c) in palette.iter_mut().enumerate() {
+ let mut lookup = [Colour::BLACK; 256];
+ for (i, c) in lookup.iter_mut().enumerate() {
match i > 0x0f {
true => *c = self.palette[i >> 4],
false => *c = self.palette[i & 0x0f],
}
};
+ // Prepare values
let b_width = buffer.width() as usize;
let b_height = buffer.height() as usize;
let s_width = self.dimensions.width() as usize;
@@ -123,8 +137,9 @@ impl ScreenDevice {
let screen_iter = zip(&self.background, &self.foreground);
let buffer_iter = buffer.as_mut_slice();
for (b, (bg, fg)) in zip(buffer_iter, screen_iter) {
- *b = palette[(fg << 4 | bg) as usize];
+ *b = lookup[(fg << 4 | bg) as usize];
}
+ // Write colours to the buffer when the size of the buffer is wrong
} else {
let width = min(b_width, s_width);
let height = min(b_height, s_height);
@@ -139,17 +154,15 @@ impl ScreenDevice {
&self.foreground[si..si+width],
);
for (b, (bg, fg)) in zip(b_iter, s_iter) {
- *b = palette[(fg << 4 | bg) as usize];
+ *b = lookup[(fg << 4 | bg) as usize];
}
- b_slice[bi+width..bi+width+width_excess].fill(palette[0]);
+ b_slice[bi+width..bi+width+width_excess].fill(lookup[0]);
bi += b_width;
si += s_width;
}
- b_slice[bi..].fill(palette[0]);
+ b_slice[bi..].fill(lookup[0]);
}
-
- // Set flags
- self.is_dirty = false;
+ self.dirty = false;
}
pub fn set_palette_high(&mut self, val: u8) {
@@ -162,24 +175,14 @@ impl ScreenDevice {
let green = (val >> 4) * 17;
let blue = (val & 0x0f) * 17;
self.palette[index] = Colour::from_rgb(red, green, blue);
- self.is_dirty = true;
- }
-
- pub fn set_sprite_colour_high(&mut self, val: u8) {
- self.sprite_colours[0] = val >> 4;
- self.sprite_colours[1] = val & 0x0f;
- }
-
- pub fn set_sprite_colour_low(&mut self, val: u8) {
- self.sprite_colours[2] = val >> 4;
- self.sprite_colours[3] = val & 0x0f;
+ self.dirty = true;
}
pub fn shunt(&mut self, val: u8) {
- let is_negative = val & 0x80 != 0;
- let is_vertical = val & 0x40 != 0;
+ let negative = test!(val, NEGATIVE);
+ let vertical = test!(val, VERTICAL);
let dist = (val & 0x3f) as u16;
- match (is_negative, is_vertical) {
+ match (negative, vertical) {
(false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist),
(false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist),
( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist),
@@ -188,31 +191,32 @@ impl ScreenDevice {
}
pub fn draw(&mut self, val: u8) {
- self.vector.push(self.cursor);
- self.is_dirty = true;
- // Parse draw byte
- let draw_mode = val & 0x70;
- let params = val & 0x0f;
+ let operation = val & 0x70;
+ let parameters = val & 0x0f;
let layer = match val & 0x80 != 0 {
true => ScreenLayer::Foreground,
false => ScreenLayer::Background
};
- match draw_mode {
- 0x00 => self.draw_pixel(params, layer, self.cursor),
- 0x10 => self.draw_sprite_1bit(params, layer),
- 0x20 => self.fill_layer(params, layer),
- 0x30 => self.draw_sprite_2bit(params, layer),
- 0x40 => self.draw_line(params, layer),
- 0x50 => self.draw_rect(params, layer),
- 0x60 => todo!("Draw 1-bit textured line"),
- 0x70 => self.draw_rect_1bit(params, layer),
+ match operation {
+ 0x00 => self.draw_pixel(parameters, layer, self.cursor),
+ 0x10 => self.draw_sprite_1bit(parameters, layer),
+ 0x20 => self.fill_layer(parameters, layer),
+ 0x30 => self.draw_sprite_2bit(parameters, layer),
+ 0x40 => self.draw_line(parameters, layer),
+ 0x50 => self.draw_rect(parameters, layer),
+ 0x60 => self.draw_line_1bit(parameters, layer),
+ 0x70 => self.draw_rect_1bit(parameters, layer),
_ => unreachable!(),
};
+
+ self.dirty = true;
+ self.vector = self.cursor;
}
+ // Draw a single pixel of a single colour
fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) {
let dim = self.dimensions;
- if !dim.contains_point(point) { return }
+ if !dim.contains_point(point) || colour > 0xf { return }
let index = point.x as usize + ((dim.width as usize) * (point.y as usize));
match layer {
ScreenLayer::Background => self.background[index] = colour,
@@ -220,6 +224,7 @@ impl ScreenDevice {
};
}
+ // Fill an entire screen layer with a single colour
fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) {
match layer {
ScreenLayer::Background => self.background.fill(colour),
@@ -227,227 +232,12 @@ impl ScreenDevice {
}
}
- fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) {
- let mut sprite = [0; 64];
- let mut pointer: usize = 0;
- let data = self.sprite_data.get_1bit_sprite();
- for row in data {
- for x in (0..8).rev() {
- sprite[pointer] = (row >> x) & 0x1;
- pointer += 1;
- }
- }
- self.draw_sprite(params, layer, sprite);
- }
-
- fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) {
- let mut sprite = [0; 64];
- let mut pointer: usize = 0;
- let data = self.sprite_data.get_2bit_sprite();
- let (spr1, spr2) = data.split_array_ref::<8>();
- for (row1, row2) in std::iter::zip(spr1, spr2) {
- for x in (0..8).rev() {
- let bit1 = (row1 >> x << 1) & 0x2;
- let bit2 = (row2 >> x) & 0x1;
- sprite[pointer] = bit1 | bit2;
- pointer += 1;
- }
- }
- self.draw_sprite(params, layer, sprite);
- }
-
- fn draw_line(&mut self, colour: u8, layer: ScreenLayer) {
- let [p0, p1] = self.vector.get_pair();
- match (p0.x == p1.x, p0.y == p1.y) {
- (false, false) => self.draw_diagonal_line(colour, layer),
- (false, true) => self.draw_horizontal_line(colour, layer),
- ( true, false) => self.draw_vertical_line(colour, layer),
- ( true, true) => self.draw_pixel(colour, layer, p0),
- };
- }
-
- fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) {
- fn abs_diff(v0: u16, v1: u16) -> u16 {
- let v = v1.wrapping_sub(v0);
- if v > 0x8000 { !v + 1 } else { v }
- }
- let [p0, p1] = self.vector.get_pair();
-
- // If the slope of the line is greater than 1.
- if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
- // Swap points 0 and 1 so that y0 is always smaller than y1.
- let (x0, y0, x1, y1) = match p0.y > p1.y {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
-
- let dy = y1 - y0;
- let (dx, xi) = match x0 > x1 {
- true => (x0 - x1, 0xffff),
- false => (x1 - x0, 0x0001),
- };
- let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
- let dx2 = dx * 2;
- let mut d = dx2.wrapping_sub(dy);
- let mut x = x0;
-
- for y in y0..=y1 {
- self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- x = x.wrapping_add(xi);
- d = d.wrapping_add(dxdy2);
- } else {
- d = d.wrapping_add(dx2);
- }
- }
- // If the slope of the line is less than or equal to 1.
- } else {
- // Swap points 0 and 1 so that x0 is always smaller than x1.
- let (x0, y0, x1, y1) = match p0.x > p1.x {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
-
- let dx = x1 - x0;
- let (dy, yi) = match y0 > y1 {
- true => (y0 - y1, 0xffff),
- false => (y1 - y0, 0x0001),
- };
- let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
- let dy2 = dy * 2;
- let mut d = dy2.wrapping_sub(dx);
- let mut y = y0;
-
- for x in x0..=x1 {
- self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- y = y.wrapping_add(yi);
- d = d.wrapping_add(dydx2);
- } else {
- d = d.wrapping_add(dy2);
- }
- }
- }
- }
-
- fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let i = screen_width * y;
- let layer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- layer[i+x0..=i+x1].fill(colour);
- }
- }
-
- fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let mut i = (screen_width * y0) + x;
- let layer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- for _ in y0..=y1 {
- layer[i] = colour;
- i += screen_width;
- }
-
- }
- }
-
- fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let rect_width = x1 - x0 + 1;
- let mut i = x0 + (screen_width * y0);
- let pixels = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- for _ in y0..=y1 {
- pixels[i..i+rect_width].fill(colour);
- i += screen_width;
- }
- }
- }
-
- fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) {
- if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let rect_width = x1 - x0 + 1;
- let mut i = x0 + (screen_width * y0);
- let pixels = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
-
- let sprite_data = self.sprite_data.get_1bit_sprite();
- let mut sprite_i = y0 % 8;
- let sprite_x_off = (x0 % 8) as u32;
- let transparent = params & 0x08 != 0;
- if params & 0x07 != 0 {
- todo!("Pre-treat sprite, with rotation/translation");
- }
-
- for _ in y0..=y1 {
- let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off);
- for _ in x0..=x1 {
- let colour = (row >> 7) as usize;
- if !(transparent && colour == 0) {
- pixels[i] = self.sprite_colours[colour];
- }
- row = row.rotate_left(1);
- i += 1;
- }
- sprite_i = (sprite_i + 1) % 8;
- i += screen_width - rect_width;
- }
- };
- }
-
- fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) {
- let transparent = params & 0x08 != 0;
- let mut position = self.cursor;
- let mut pointer: usize = 0;
-
- macro_rules! for8 { ($block:block) => { for _ in 0..8 { $block } }; }
- macro_rules! r { ($v:expr) => { position.x = position.x.wrapping_add($v) }; }
- macro_rules! l { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; }
- macro_rules! d { ($v:expr) => { position.y = position.y.wrapping_add($v) }; }
- macro_rules! u { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; }
- macro_rules! px { () => {
- let colour = sprite[pointer];
- if !(transparent && colour == 0) {
- self.draw_pixel(self.sprite_colours[colour as usize], layer, position);
- }
- pointer += 1;
- }; }
-
-
- match params & 0x07 {
- 0 => { for8!{{ for8!{{ px!(); r!(1); }} l!(8); d!(1); }} }
- 1 => { r!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); d!(1); }} }
- 2 => { d!(7); for8!{{ for8!{{ px!(); r!(1); }} l!(8); u!(1); }} }
- 3 => { r!(7); d!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); u!(1); }} }
-
- 4 => { for8!{{ for8!{{ px!(); d!(1); }} u!(8); r!(1); }} }
- 5 => { r!(7); for8!{{ for8!{{ px!(); d!(1); }} u!(8); l!(1); }} }
- 6 => { d!(7);for8!{{ for8!{{ px!(); u!(1); }} d!(8); r!(1); }} }
- 7 => { r!(7); d!(7); for8!{{ for8!{{ px!(); u!(1); }} d!(8); l!(1); }} }
-
- _ => unreachable!(),
- }
- }
-
- /// Returns [x0, y0, x1, y1]
+ /// Returns [x0, y0, x1, y1], ensuring that x0 <= x1 and y0 <= y1
fn find_vector_bounding_box(&self) -> Option<[usize; 4]> {
macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};}
macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};}
- let [p0, p1] = self.vector.get_pair();
+ let [p0, p1] = [self.cursor, self.vector];
let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ];
let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ];
let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ];