Skip to content

Commit

Permalink
Add support for transparency
Browse files Browse the repository at this point in the history
  • Loading branch information
caspark committed Apr 8, 2024
1 parent c84bd56 commit d0ca80f
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 75 deletions.
8 changes: 4 additions & 4 deletions benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};

use crab_tv::{Canvas, WHITE};
use glam::IVec2;
use rgb::RGB8;
use rgb::RGBA8;

fn line_drawing(c: &mut Criterion) {
let mut group = c.benchmark_group("line-drawing");

group.bench_function("v1-slow", |b| {
let mut image = Canvas::new(100, 100);
b.iter(|| {
image.line_slow(0, 0, 99, 99, RGB8::new(255, 0, 0));
image.line_slow(0, 0, 99, 99, RGBA8::new(255, 0, 0));
});
black_box(image);
});

group.bench_function("v2-faster", |b| {
let mut image = Canvas::new(100, 100);
b.iter(|| {
image.line_faster(0, 0, 99, 99, RGB8::new(255, 0, 0));
image.line_faster(0, 0, 99, 99, RGBA8::new(255, 0, 0));
});
black_box(image);
});

group.bench_function("v3-integer maths", |b| {
let mut image = Canvas::new(100, 100);
b.iter(|| {
image.line_fastest(0, 0, 99, 99, RGB8::new(255, 0, 0));
image.line_fastest(0, 0, 99, 99, RGBA8::new(255, 0, 0));
});
black_box(image);
});
Expand Down
25 changes: 10 additions & 15 deletions src/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::f32::consts::PI;

use glam::{Mat3, Vec2, Vec3};
use rgb::{ComponentMap, RGB8};
use rgb::{ComponentMap, RGBA8};

use crate::{
maths::{self, yolo_max, yolo_min},
Model, DEPTH_MAX,
maths::{self, yolo_max, yolo_min}, Model, CLEAR, DEPTH_MAX
};

#[derive(Copy, Clone, Debug, PartialEq, Default)]
Expand All @@ -17,14 +16,14 @@ pub struct Vertex {

pub trait Shader<S> {
fn vertex(&self, triangle: [Vertex; 3]) -> (Mat3, S);
fn fragment(&self, barycentric_coords: Vec3, state: &S) -> Option<RGB8>;
fn fragment(&self, barycentric_coords: Vec3, state: &S) -> Option<RGBA8>;
}

#[derive(Clone, Debug)]
pub struct Canvas {
width: usize,
height: usize,
pixels: Vec<RGB8>,
pixels: Vec<RGBA8>,
z_buffer: Vec<f32>,
}

Expand All @@ -33,7 +32,7 @@ impl Canvas {
Self {
width,
height,
pixels: vec![RGB8::default(); width * height],
pixels: vec![RGBA8::default(); width * height],
z_buffer: vec![f32::NEG_INFINITY; width * height],
}
}
Expand All @@ -48,20 +47,16 @@ impl Canvas {
self.height
}

pub fn pixels(&self) -> &[RGB8] {
pub fn pixels(&self) -> &[RGBA8] {
&self.pixels
}

pub fn pixels_mut(&mut self) -> &mut [RGB8] {
pub fn pixels_mut(&mut self) -> &mut [RGBA8] {
&mut self.pixels
}

pub fn into_pixels(self) -> Vec<RGB8> {
self.pixels
}

#[inline]
pub fn pixel(&self, x: i32, y: i32) -> RGB8 {
pub fn pixel(&self, x: i32, y: i32) -> RGBA8 {
debug_assert!(
x >= 0 && x < self.width as i32,
"x coordinate of '{}' is out of bounds 0 to {}",
Expand All @@ -78,7 +73,7 @@ impl Canvas {
}

#[inline]
pub fn pixel_mut(&mut self, x: i32, y: i32) -> &mut RGB8 {
pub fn pixel_mut(&mut self, x: i32, y: i32) -> &mut RGBA8 {
debug_assert!(
x >= 0 && x < self.width as i32,
"x coordinate of '{}' is out of bounds 0 to {}",
Expand Down Expand Up @@ -133,7 +128,7 @@ impl Canvas {
.z_buffer
.iter()
.map(|d| (*d * 255.0 / DEPTH_MAX) as u8)
.map(|c| RGB8::new(c, c, c))
.map(|c| RGBA8::new(c, c, c, 255))
.collect();
}

Expand Down
54 changes: 27 additions & 27 deletions src/canvas_legacy.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// Legacy canvas API, where only certain fixed functions are supported (no shaders).
use glam::{IVec2, Mat4, Vec2, Vec3, Vec4};
use rgb::{ComponentMap, RGB8};
use rgb::{ComponentMap, RGBA8};

use crate::{
maths::{self, yolo_max, yolo_min},
Expand All @@ -19,7 +19,7 @@ pub enum ModelShading {
impl Canvas {
// incorrect because it depends on choosing the correct "increment", which will vary based on
// how many pixels need to be drawn
pub fn line_naive1(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) {
pub fn line_naive1(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGBA8) {
let increment = 0.1;
for i in 0..((1.0 / increment) as i32) {
let i = f64::from(i) * increment;
Expand All @@ -30,16 +30,16 @@ impl Canvas {
}

// incorrect because it doesn't handle the case where the line is near vertical or x1 < x0
pub fn line_naive2(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) {
pub fn line_naive2(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGBA8) {
for x in x0..x1 {
let t = (x - x0) as f64 / (x1 - x0) as f64;
let y = y0 as f64 * (1.0 - t) as f64 + y1 as f64 * t as f64;
*self.pixel_mut(x as i32, y as i32) = color;
let y = y0 as f64 * (1.0 - t) + y1 as f64 * t;
*self.pixel_mut(x, y as i32) = color;
}
}

// Bresenham's algorithm 1 - correct but slow due to needing floating point maths
pub fn line_slow(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGB8) {
pub fn line_slow(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGBA8) {
let steep = if (x0 - x1).abs() < (y0 - y1).abs() {
std::mem::swap(&mut x0, &mut y0);
std::mem::swap(&mut x1, &mut y1);
Expand All @@ -56,17 +56,17 @@ impl Canvas {
let divisor = x1 - x0;
for x in x0..x1 {
let t = (x - x0) as f64 / divisor as f64;
let y = y0 as f64 * (1.0 - t) as f64 + y1 as f64 * t as f64;
let y = y0 as f64 * (1.0 - t) + y1 as f64 * t;
if steep {
*self.pixel_mut(y as i32, x as i32) = color;
*self.pixel_mut(y as i32, x) = color;
} else {
*self.pixel_mut(x as i32, y as i32) = color;
*self.pixel_mut(x, y as i32) = color;
}
}
}

// Bresenham's algorithm 2 - still using floating point maths but avoiding some division
pub fn line_faster(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGB8) {
pub fn line_faster(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGBA8) {
let steep = if (x0 - x1).abs() < (y0 - y1).abs() {
std::mem::swap(&mut x0, &mut y0);
std::mem::swap(&mut x1, &mut y1);
Expand Down Expand Up @@ -106,7 +106,7 @@ impl Canvas {
mut y0: i32,
mut x1: i32,
mut y1: i32,
color: RGB8,
color: RGBA8,
) {
let steep = if (x0 - x1).abs() < (y0 - y1).abs() {
std::mem::swap(&mut x0, &mut y0);
Expand All @@ -128,9 +128,9 @@ impl Canvas {
let mut y = y0;
for x in x0..=x1 {
if steep {
*self.pixel_mut(y as i32, x as i32) = color;
*self.pixel_mut(y, x) = color;
} else {
*self.pixel_mut(x as i32, y as i32) = color;
*self.pixel_mut(x, y) = color;
}
error2 += derror2;
if error2 > dx {
Expand All @@ -140,13 +140,13 @@ impl Canvas {
}
}

pub fn line(&mut self, p1: IVec2, p2: IVec2, color: RGB8) {
pub fn line(&mut self, p1: IVec2, p2: IVec2, color: RGBA8) {
let (x0, y0) = (p1.x, p1.y);
let (x1, y1) = (p2.x, p2.y);
self.line_fastest(x0, y0, x1, y1, color);
}

pub fn model_wireframe(&mut self, model: &Model, color: RGB8) {
pub fn model_wireframe(&mut self, model: &Model, color: RGBA8) {
for face in model.faces.iter() {
for j in 0..3 {
let v0 = model.vertices[face.points[j].vertices_index];
Expand Down Expand Up @@ -301,10 +301,10 @@ impl Canvas {
let w = (avg_intensity * 255.0) as u8;
match shading {
ModelShading::FlatOnly => {
self.triangle_barycentric(&screen_coords_2d, RGB8::new(w, w, w))
self.triangle_barycentric(&screen_coords_2d, RGBA8::new(w, w, w, 255))
}
ModelShading::DepthTested => self
.triangle_barycentric_depth_tested(&screen_coords_3d, RGB8::new(w, w, w)),
.triangle_barycentric_depth_tested(&screen_coords_3d, RGBA8::new(w, w, w, 255)),
ModelShading::Textured => self.triangle_barycentric_texture(
&screen_coords_3d,
&model.diffuse_texture,
Expand All @@ -323,7 +323,7 @@ impl Canvas {
}

/// Output a wireframe (unfilled) triangle by using line drawing
pub fn triangle_wireframe(&mut self, t0: IVec2, t1: IVec2, t2: IVec2, color: RGB8) {
pub fn triangle_wireframe(&mut self, t0: IVec2, t1: IVec2, t2: IVec2, color: RGBA8) {
self.line(t0, t1, color);
self.line(t1, t2, color);
self.line(t2, t0, color);
Expand All @@ -340,13 +340,13 @@ impl Canvas {
(vertices[0], vertices[1], vertices[2])
};

self.line(t2, t0, RGB8::new(255, 0, 0));
self.line(t0, t1, RGB8::new(0, 255, 0));
self.line(t1, t2, RGB8::new(0, 0, 255));
self.line(t2, t0, RGBA8::new(255, 0, 0, 255));
self.line(t0, t1, RGBA8::new(0, 255, 0, 255));
self.line(t1, t2, RGBA8::new(0, 0, 255, 255));
}

// Draw a filled triangle using line sweeping.
pub fn triangle_linesweep_verbose(&mut self, pts: &[IVec2], color: RGB8) {
pub fn triangle_linesweep_verbose(&mut self, pts: &[IVec2], color: RGBA8) {
let (t0, t1, t2) = (pts[0], pts[1], pts[2]);

if t0.y == t1.y && t0.y == t2.y {
Expand Down Expand Up @@ -416,7 +416,7 @@ impl Canvas {
}

// Draw a filled triangle using line sweeping, approach 2
pub fn triangle_linesweep_compact(&mut self, pts: &[IVec2], color: RGB8) {
pub fn triangle_linesweep_compact(&mut self, pts: &[IVec2], color: RGBA8) {
let (t0, t1, t2) = (pts[0], pts[1], pts[2]);

if t0.y == t1.y && t0.y == t2.y {
Expand Down Expand Up @@ -457,7 +457,7 @@ impl Canvas {
}
}

pub fn triangle_barycentric(&mut self, pts: &[IVec2], color: RGB8) {
pub fn triangle_barycentric(&mut self, pts: &[IVec2], color: RGBA8) {
let mut bboxmin = IVec2::new((self.width() - 1) as i32, (self.height() - 1) as i32);
let mut bboxmax = IVec2::new(0, 0);
let clamp = IVec2::new((self.width() - 1) as i32, (self.height() - 1) as i32);
Expand All @@ -481,7 +481,7 @@ impl Canvas {
}
}

pub fn triangle_barycentric_depth_tested(&mut self, pts: &[Vec3], color: RGB8) {
pub fn triangle_barycentric_depth_tested(&mut self, pts: &[Vec3], color: RGBA8) {
let mut bboxmin = Vec2::new((self.width() - 1) as f32, (self.height() - 1) as f32);
let mut bboxmax = Vec2::new(0.0, 0.0);
let clamp = Vec2::new((self.width() - 1) as f32, (self.height() - 1) as f32);
Expand Down Expand Up @@ -553,7 +553,7 @@ impl Canvas {
let color = tex.data[(tex.height - uv.y as usize) * tex.width + uv.x as usize]
.map(|comp| (comp as f32 * light_intensity) as u8);

*self.pixel_mut(i, j) = color;
*self.pixel_mut(i, j) = color.into();
}
}
}
Expand Down Expand Up @@ -604,7 +604,7 @@ impl Canvas {
let color = tex.data[(tex.height - uv.y as usize) * tex.width + uv.x as usize]
.map(|comp| (comp as f32 * weighted_light_intensity) as u8);

*self.pixel_mut(i, j) = color;
*self.pixel_mut(i, j) = color.into();
}
}
}
Expand Down
24 changes: 13 additions & 11 deletions src/colors.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use rgb::RGB8;
use rgb::RGBA8;

pub const WHITE: RGB8 = RGB8::new(255, 255, 255);
pub const BLACK: RGB8 = RGB8::new(0, 0, 0);
pub const WHITE: RGBA8 = RGBA8::new(255, 255, 255, 255);
pub const BLACK: RGBA8 = RGBA8::new(0, 0, 0, 255);
pub const CLEAR: RGBA8 = RGBA8::new(100, 100, 100, 0);

pub const RED: RGB8 = RGB8::new(255, 0, 0);
pub const GREEN: RGB8 = RGB8::new(0, 255, 0);
pub const BLUE: RGB8 = RGB8::new(0, 0, 255);
pub const RED: RGBA8 = RGBA8::new(255, 0, 0, 255);
pub const GREEN: RGBA8 = RGBA8::new(0, 255, 0, 255);
pub const BLUE: RGBA8 = RGBA8::new(0, 0, 255, 255);

pub const YELLOW: RGB8 = RGB8::new(255, 255, 0);
pub const CYAN: RGB8 = RGB8::new(0, 255, 255);
pub const MAGENTA: RGB8 = RGB8::new(255, 0, 255);
pub const YELLOW: RGBA8 = RGBA8::new(255, 255, 0, 255);
pub const CYAN: RGBA8 = RGBA8::new(0, 255, 255, 255);
pub const MAGENTA: RGBA8 = RGBA8::new(255, 0, 255, 255);

pub fn random_color() -> RGB8 {
RGB8::new(
pub fn random_color() -> RGBA8 {
RGBA8::new(
rand::random::<u8>() % 255,
rand::random::<u8>() % 255,
rand::random::<u8>() % 255,
255,
)
}
8 changes: 4 additions & 4 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context, Result};
use derive_more::Constructor;
use glam::{Vec2, Vec3};
use rgb::{ComponentMap, RGB8};
use rgb::{ComponentMap, RGB8, RGBA8};

#[derive(Clone, Copy, Debug, PartialEq, Constructor)]
pub struct Vertex {
Expand Down Expand Up @@ -50,13 +50,13 @@ impl Texture {
))
}

pub fn get_pixel(&self, uv: Vec2) -> RGB8 {
pub fn get_pixel(&self, uv: Vec2) -> RGBA8 {
let x = uv.x as usize;
let y = uv.y as usize;
debug_assert!(x < self.width);
debug_assert!(y < self.height);

self.data[(self.height - y as usize) * self.width + x as usize]
self.data[(self.height - y) * self.width + x].into()
}

pub fn get_normal(&self, uv: Vec2) -> Vec3 {
Expand Down Expand Up @@ -254,7 +254,7 @@ impl Model {
let specular_texture = Texture::load_from_file(&input.specular_texture)
.context("Loading specular texture failed")?;
let glow_texture = input.glow_texture.as_ref().and_then(|texture| {
Texture::load_from_file(&texture)
Texture::load_from_file(texture)
.context("Loading glow texture failed")
.ok()
});
Expand Down
Loading

0 comments on commit d0ca80f

Please sign in to comment.