diff --git a/Cargo.toml b/Cargo.toml index e931d8d..653dc02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,7 @@ bitflags = "1.2.1" conquer-once = { version = "0.2.0", default-features = false } spinning_top = { version = "0.1.0", features = ["nightly"] } x86_64 = "0.9.6" + +[dependencies.num-traits] +version = "0.2.11" +default-features = false diff --git a/src/drawing/bresenham.rs b/src/drawing/bresenham.rs new file mode 100644 index 0000000..6a13e72 --- /dev/null +++ b/src/drawing/bresenham.rs @@ -0,0 +1,57 @@ +use super::{Octant, Point, SignedNum}; + +pub(crate) struct Bresenham { + point: Point, + end_x: T, + delta_x: T, + delta_y: T, + error: T, + octant: Octant, +} + +impl Bresenham { + #[inline] + pub fn new(start: Point, end: Point) -> Self { + let octant = Octant::new(start, end); + let start = octant.to(start); + let end = octant.to(end); + + let delta_x = end.0 - start.0; + let delta_y = end.1 - start.1; + + Self { + delta_x, + delta_y, + octant, + point: start, + end_x: end.0, + error: delta_y - delta_x, + } + } +} + +impl Iterator for Bresenham +where + T: SignedNum, +{ + type Item = Point; + + #[inline] + fn next(&mut self) -> Option { + if self.point.0 <= self.end_x { + let point = self.octant.from(self.point); + + if self.error >= T::zero() { + self.point.1 += T::one(); + self.error -= self.delta_x; + } + + self.point.0 += T::one(); + self.error += self.delta_y; + + Some(point) + } else { + None + } + } +} diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs new file mode 100644 index 0000000..5d15c03 --- /dev/null +++ b/src/drawing/mod.rs @@ -0,0 +1,20 @@ +//! Common functionality for drawing in vga graphics mode. +//! Original implementation here https://github.com/expenses/line_drawing. +use num_traits::{NumAssignOps, NumCast, Signed}; + +mod bresenham; +mod octant; + +pub(crate) use bresenham::Bresenham; +use octant::Octant; + +/// A point in 2D space. +pub type Point = (T, T); + +pub(crate) trait SignedNum: Signed + Ord + Copy + NumCast + NumAssignOps { + fn cast(value: T) -> Self { + NumCast::from(value).unwrap() + } +} + +impl SignedNum for T {} diff --git a/src/drawing/octant.rs b/src/drawing/octant.rs new file mode 100644 index 0000000..caf2431 --- /dev/null +++ b/src/drawing/octant.rs @@ -0,0 +1,75 @@ +use super::Point; +use core::ops::{Neg, Sub}; +use num_traits::Zero; + +/// A simple octant struct for transforming line points. +pub struct Octant { + value: u8, +} + +impl Octant { + #[inline] + /// Get the relevant octant from a start and end point. + pub fn new(start: Point, end: Point) -> Self + where + T: Sub + Neg + PartialOrd + Zero, + { + let mut value = 0; + let mut dx = end.0 - start.0; + let mut dy = end.1 - start.1; + + if dy < T::zero() { + dx = -dx; + dy = -dy; + value += 4; + } + + if dx < T::zero() { + let tmp = dx; + dx = dy; + dy = -tmp; + value += 2; + } + + if dx < dy { + value += 1; + } + + Self { value } + } + + /// Convert a point to its position in the octant. + #[inline] + pub fn to(&self, point: Point) -> Point + where + T: Neg, + { + match self.value { + 0 => (point.0, point.1), + 1 => (point.1, point.0), + 2 => (point.1, -point.0), + 3 => (-point.0, point.1), + 4 => (-point.0, -point.1), + 5 => (-point.1, -point.0), + 6 => (-point.1, point.0), + 7 => (point.0, -point.1), + _ => unreachable!(), + } + } + + /// Convert a point from its position in the octant. + #[inline] + pub fn from>(&self, point: Point) -> Point { + match self.value { + 0 => (point.0, point.1), + 1 => (point.1, point.0), + 2 => (-point.1, point.0), + 3 => (-point.0, point.1), + 4 => (-point.0, -point.1), + 5 => (-point.1, -point.0), + 6 => (point.1, -point.0), + 7 => (point.0, -point.1), + _ => unreachable!(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 7b11932..06b9ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod colors; pub mod configurations; +pub mod drawing; pub mod fonts; pub mod registers; pub mod vga; diff --git a/src/registers/graphics_controller.rs b/src/registers/graphics_controller.rs index 8d3c78f..c4afa41 100644 --- a/src/registers/graphics_controller.rs +++ b/src/registers/graphics_controller.rs @@ -1,4 +1,4 @@ -use super::{Color16Bit, PlaneMask, GRX_DATA_ADDRESS, GRX_INDEX_ADDRESS}; +use super::{Color16Bit, GRX_DATA_ADDRESS, GRX_INDEX_ADDRESS}; use core::convert::TryFrom; use x86_64::instructions::port::Port; @@ -177,13 +177,13 @@ impl GraphicsControllerRegisters { ); } - /// Sets which planes are effected by `GraphicsControllerIndex::SetReset`, - /// as specified by `plane_mask`. - pub fn write_enable_set_reset(&mut self, plane_mask: PlaneMask) { + /// Sets which bits are effected by `GraphicsControllerIndex::SetReset`, + /// as specified by `bit_mask`. + pub fn write_enable_set_reset(&mut self, bit_mask: u8) { let original_value = self.read(GraphicsControllerIndex::EnableSetReset) & 0xF0; self.write( GraphicsControllerIndex::EnableSetReset, - original_value | u8::from(plane_mask), + original_value | bit_mask, ); } diff --git a/src/writers/graphics_640x480x16.rs b/src/writers/graphics_640x480x16.rs index 258c503..1f59122 100644 --- a/src/writers/graphics_640x480x16.rs +++ b/src/writers/graphics_640x480x16.rs @@ -1,5 +1,6 @@ use crate::{ colors::{Color16Bit, DEFAULT_PALETTE}, + drawing::{Bresenham, Point}, registers::{PlaneMask, WriteMode}, vga::{Vga, VideoMode, VGA}, }; @@ -17,12 +18,13 @@ const WIDTH_IN_BYTES: usize = WIDTH / 8; /// Basic usage: /// /// ```no_run +/// use vga::colors::Color16Bit; /// use vga::writers::Graphics640x480x16; /// /// let graphics_mode = Graphics640x480x16::new(); /// /// graphics_mode.set_mode(); -/// graphics_mode.clear_screen(); +/// graphics_mode.clear_screen(Color16Bit::Black); /// ``` #[derive(Default)] pub struct Graphics640x480x16; @@ -36,14 +38,11 @@ impl Graphics640x480x16 { /// Clears the screen by setting all pixels to the specified `color`. pub fn clear_screen(&self, color: Color16Bit) { let (mut vga, frame_buffer) = self.get_frame_buffer(); - // Set write mode 2 so data is modified by the bitmask vga.graphics_controller_registers .set_write_mode(WriteMode::Mode2); - // Write to all 4 planes at once + vga.graphics_controller_registers.set_bit_mask(0xFF); vga.sequencer_registers .set_plane_mask(PlaneMask::ALL_PLANES); - // Every bit should be set to the same color - vga.graphics_controller_registers.set_bit_mask(0xFF); for offset in 0..ALL_PLANES_SCREEN_SIZE { unsafe { frame_buffer.add(offset).write_volatile(u8::from(color)); @@ -51,21 +50,32 @@ impl Graphics640x480x16 { } } + /// Draws a line from `start` to `end` with the specified `color`. + pub fn draw_line(&self, start: Point, end: Point, color: Color16Bit) { + { + let (mut vga, _frame_buffer) = self.get_frame_buffer(); + vga.graphics_controller_registers.write_set_reset(color); + vga.graphics_controller_registers + .write_enable_set_reset(0xF); + vga.graphics_controller_registers + .set_write_mode(WriteMode::Mode0); + } + + for (x, y) in Bresenham::new(start, end) { + self.set_pixel_with_set_reset(x as usize, y as usize); + } + } + /// Sets the given pixel at `(x, y)` to the given `color`. pub fn set_pixel(&self, x: usize, y: usize, color: Color16Bit) { let (mut vga, frame_buffer) = self.get_frame_buffer(); let offset = x / 8 + y * WIDTH_IN_BYTES; // Which pixel to modify this write - let pixel_offset = x & 7; - // Set write mode 2 so screen data is only modified by the bitmask + let pixel_mask = 0x80 >> (x & 0x07); vga.graphics_controller_registers .set_write_mode(WriteMode::Mode2); - // Write to all 4 planes at once - vga.sequencer_registers - .set_plane_mask(PlaneMask::ALL_PLANES); // Only modify 1 pixel, based on the offset - vga.graphics_controller_registers - .set_bit_mask(1 << pixel_offset); + vga.graphics_controller_registers.set_bit_mask(pixel_mask); unsafe { // Reads the current offset into the memory latches frame_buffer.add(offset).read_volatile(); @@ -75,6 +85,22 @@ impl Graphics640x480x16 { } } + fn set_pixel_with_set_reset(&self, x: usize, y: usize) { + let (mut vga, frame_buffer) = self.get_frame_buffer(); + let offset = x / 8 + y * WIDTH_IN_BYTES; + // Which pixel to modify this write + let pixel_mask = 0x80 >> (x & 0x07); + // Only modify 1 pixel, based on the offset + vga.graphics_controller_registers.set_bit_mask(pixel_mask); + unsafe { + // Reads the current offset into the memory latches + frame_buffer.add(offset).read_volatile(); + // Sets the pixel specified by the offset to the color. The + // pixels not inlcuded in the bit mask remain untouched. + frame_buffer.add(offset).write_volatile(0x00); + } + } + /// Sets the graphics device to `VideoMode::Mode640x480x16`. pub fn set_mode(&self) { let mut vga = VGA.lock();