Rough draft line drawing
This commit is contained in:
parent
ec9f652de0
commit
d5dec0159c
|
@ -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
57
src/drawing/bresenham.rs
Normal 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
20
src/drawing/mod.rs
Normal 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
75
src/drawing/octant.rs
Normal 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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue