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 }
|
||||
spinning_top = { version = "0.1.0", features = ["nightly"] }
|
||||
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 configurations;
|
||||
pub mod drawing;
|
||||
pub mod fonts;
|
||||
pub mod registers;
|
||||
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 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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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`.
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue