Rough draft line drawing

This commit is contained in:
Ryan Kennedy 2020-03-23 17:03:41 -05:00
parent ec9f652de0
commit d5dec0159c
7 changed files with 200 additions and 17 deletions

View file

@ -23,3 +23,7 @@ bitflags = "1.2.1"
conquer-once = { version = "0.2.0", default-features = false } conquer-once = { version = "0.2.0", default-features = false }
spinning_top = { version = "0.1.0", features = ["nightly"] } spinning_top = { version = "0.1.0", features = ["nightly"] }
x86_64 = "0.9.6" x86_64 = "0.9.6"
[dependencies.num-traits]
version = "0.2.11"
default-features = false

57
src/drawing/bresenham.rs Normal file
View file

@ -0,0 +1,57 @@
use super::{Octant, Point, SignedNum};
pub(crate) struct Bresenham<T> {
point: Point<T>,
end_x: T,
delta_x: T,
delta_y: T,
error: T,
octant: Octant,
}
impl<T: SignedNum> Bresenham<T> {
#[inline]
pub fn new(start: Point<T>, end: Point<T>) -> 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<T> Iterator for Bresenham<T>
where
T: SignedNum,
{
type Item = Point<T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
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
}
}
}

20
src/drawing/mod.rs Normal file
View file

@ -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, T);
pub(crate) trait SignedNum: Signed + Ord + Copy + NumCast + NumAssignOps {
fn cast<T: NumCast>(value: T) -> Self {
NumCast::from(value).unwrap()
}
}
impl<T: Signed + Ord + Copy + NumCast + NumAssignOps> SignedNum for T {}

75
src/drawing/octant.rs Normal file
View file

@ -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<T>(start: Point<T>, end: Point<T>) -> Self
where
T: Sub<Output = T> + Neg<Output = T> + 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<T>(&self, point: Point<T>) -> Point<T>
where
T: Neg<Output = T>,
{
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<T: Neg<Output = T>>(&self, point: Point<T>) -> Point<T> {
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!(),
}
}
}

View file

@ -9,6 +9,7 @@
pub mod colors; pub mod colors;
pub mod configurations; pub mod configurations;
pub mod drawing;
pub mod fonts; pub mod fonts;
pub mod registers; pub mod registers;
pub mod vga; pub mod vga;

View file

@ -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 core::convert::TryFrom;
use x86_64::instructions::port::Port; use x86_64::instructions::port::Port;
@ -177,13 +177,13 @@ impl GraphicsControllerRegisters {
); );
} }
/// Sets which planes are effected by `GraphicsControllerIndex::SetReset`, /// Sets which bits are effected by `GraphicsControllerIndex::SetReset`,
/// as specified by `plane_mask`. /// as specified by `bit_mask`.
pub fn write_enable_set_reset(&mut self, plane_mask: PlaneMask) { pub fn write_enable_set_reset(&mut self, bit_mask: u8) {
let original_value = self.read(GraphicsControllerIndex::EnableSetReset) & 0xF0; let original_value = self.read(GraphicsControllerIndex::EnableSetReset) & 0xF0;
self.write( self.write(
GraphicsControllerIndex::EnableSetReset, GraphicsControllerIndex::EnableSetReset,
original_value | u8::from(plane_mask), original_value | bit_mask,
); );
} }

View file

@ -1,5 +1,6 @@
use crate::{ use crate::{
colors::{Color16Bit, DEFAULT_PALETTE}, colors::{Color16Bit, DEFAULT_PALETTE},
drawing::{Bresenham, Point},
registers::{PlaneMask, WriteMode}, registers::{PlaneMask, WriteMode},
vga::{Vga, VideoMode, VGA}, vga::{Vga, VideoMode, VGA},
}; };
@ -17,12 +18,13 @@ const WIDTH_IN_BYTES: usize = WIDTH / 8;
/// Basic usage: /// Basic usage:
/// ///
/// ```no_run /// ```no_run
/// use vga::colors::Color16Bit;
/// use vga::writers::Graphics640x480x16; /// use vga::writers::Graphics640x480x16;
/// ///
/// let graphics_mode = Graphics640x480x16::new(); /// let graphics_mode = Graphics640x480x16::new();
/// ///
/// graphics_mode.set_mode(); /// graphics_mode.set_mode();
/// graphics_mode.clear_screen(); /// graphics_mode.clear_screen(Color16Bit::Black);
/// ``` /// ```
#[derive(Default)] #[derive(Default)]
pub struct Graphics640x480x16; pub struct Graphics640x480x16;
@ -36,14 +38,11 @@ impl Graphics640x480x16 {
/// Clears the screen by setting all pixels to the specified `color`. /// Clears the screen by setting all pixels to the specified `color`.
pub fn clear_screen(&self, color: Color16Bit) { pub fn clear_screen(&self, color: Color16Bit) {
let (mut vga, frame_buffer) = self.get_frame_buffer(); let (mut vga, frame_buffer) = self.get_frame_buffer();
// Set write mode 2 so data is modified by the bitmask
vga.graphics_controller_registers vga.graphics_controller_registers
.set_write_mode(WriteMode::Mode2); .set_write_mode(WriteMode::Mode2);
// Write to all 4 planes at once vga.graphics_controller_registers.set_bit_mask(0xFF);
vga.sequencer_registers vga.sequencer_registers
.set_plane_mask(PlaneMask::ALL_PLANES); .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 { for offset in 0..ALL_PLANES_SCREEN_SIZE {
unsafe { unsafe {
frame_buffer.add(offset).write_volatile(u8::from(color)); 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<isize>, end: Point<isize>, 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`. /// Sets the given pixel at `(x, y)` to the given `color`.
pub fn set_pixel(&self, x: usize, y: usize, color: Color16Bit) { pub fn set_pixel(&self, x: usize, y: usize, color: Color16Bit) {
let (mut vga, frame_buffer) = self.get_frame_buffer(); let (mut vga, frame_buffer) = self.get_frame_buffer();
let offset = x / 8 + y * WIDTH_IN_BYTES; let offset = x / 8 + y * WIDTH_IN_BYTES;
// Which pixel to modify this write // Which pixel to modify this write
let pixel_offset = x & 7; let pixel_mask = 0x80 >> (x & 0x07);
// Set write mode 2 so screen data is only modified by the bitmask
vga.graphics_controller_registers vga.graphics_controller_registers
.set_write_mode(WriteMode::Mode2); .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 // Only modify 1 pixel, based on the offset
vga.graphics_controller_registers vga.graphics_controller_registers.set_bit_mask(pixel_mask);
.set_bit_mask(1 << pixel_offset);
unsafe { unsafe {
// Reads the current offset into the memory latches // Reads the current offset into the memory latches
frame_buffer.add(offset).read_volatile(); 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`. /// Sets the graphics device to `VideoMode::Mode640x480x16`.
pub fn set_mode(&self) { pub fn set_mode(&self) {
let mut vga = VGA.lock(); let mut vga = VGA.lock();