summaryrefslogtreecommitdiff
path: root/racer-tracer
diff options
context:
space:
mode:
authorSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-01-10 17:54:39 +0100
committerSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-01-11 19:36:38 +0100
commitbbecf67545c2bd6822b0680673aa850c5ddef9f3 (patch)
tree741b2fc03797a8fedcfe67e34687b4443fcf41f7 /racer-tracer
parent899f81eed6c221dce22333ad03704b12d7634a54 (diff)
downloadracer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.tar.gz
racer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.tar.xz
racer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.zip
🔨 Refactors & Use rayon
- All data shared between threads are now Arcs since the data is immutable. - Remove tokio - Rustified main
Diffstat (limited to 'racer-tracer')
-rw-r--r--racer-tracer/Cargo.toml2
-rw-r--r--racer-tracer/src/error.rs2
-rw-r--r--racer-tracer/src/geometry.rs4
-rw-r--r--racer-tracer/src/geometry/sphere.rs14
-rw-r--r--racer-tracer/src/image.rs1
-rw-r--r--racer-tracer/src/main.rs177
-rw-r--r--racer-tracer/src/scene.rs24
7 files changed, 106 insertions, 118 deletions
diff --git a/racer-tracer/Cargo.toml b/racer-tracer/Cargo.toml
index 2083dcf..241e1d8 100644
--- a/racer-tracer/Cargo.toml
+++ b/racer-tracer/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
minifb = "0.23"
-tokio = { version = "1.14.0", features = ["macros", "rt", "rt-multi-thread"] }
thiserror = "1"
futures = "0.3"
rand = "0.8.5"
+rayon = "1.6.1"
diff --git a/racer-tracer/src/error.rs b/racer-tracer/src/error.rs
index f581b08..ac78c91 100644
--- a/racer-tracer/src/error.rs
+++ b/racer-tracer/src/error.rs
@@ -1,6 +1,6 @@
use thiserror::Error;
-#[derive(Error, Debug)]
+#[derive(Clone, Error, Debug)]
pub enum TracerError {
#[error("Unknown error: {message}")]
Unknown { message: String, exit_code: i32 },
diff --git a/racer-tracer/src/geometry.rs b/racer-tracer/src/geometry.rs
index aa1c18c..8d86353 100644
--- a/racer-tracer/src/geometry.rs
+++ b/racer-tracer/src/geometry.rs
@@ -47,8 +47,4 @@ impl Default for HitRecord {
pub trait Hittable {
//pub trait Hittable {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
-
- // Manual ugly clone since forcing on Clone would make it a super trait.
- // Clone requires Sized and super traits cannot be Sized.
- fn clone_box(&self) -> Box<dyn Hittable>;
}
diff --git a/racer-tracer/src/geometry/sphere.rs b/racer-tracer/src/geometry/sphere.rs
index 6a0ab9c..e974c44 100644
--- a/racer-tracer/src/geometry/sphere.rs
+++ b/racer-tracer/src/geometry/sphere.rs
@@ -20,16 +20,6 @@ impl Sphere {
}
}
-impl Clone for Sphere {
- fn clone(&self) -> Self {
- Self {
- pos: self.pos,
- radius: self.radius,
- material: self.material,
- }
- }
-}
-
impl Hittable for Sphere {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = ray.origin() - self.pos;
@@ -61,8 +51,4 @@ impl Hittable for Sphere {
hit_record.set_face_normal(ray, outward_normal);
Some(hit_record)
}
-
- fn clone_box(&self) -> Box<dyn Hittable> {
- Box::new(self.clone())
- }
}
diff --git a/racer-tracer/src/image.rs b/racer-tracer/src/image.rs
index 2df11dc..48e81ee 100644
--- a/racer-tracer/src/image.rs
+++ b/racer-tracer/src/image.rs
@@ -1,4 +1,3 @@
-#[derive(Clone)]
pub struct Image {
pub aspect_ratio: f64,
pub width: usize,
diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs
index 9d45762..c9ed169 100644
--- a/racer-tracer/src/main.rs
+++ b/racer-tracer/src/main.rs
@@ -8,13 +8,18 @@ mod scene;
mod util;
mod vec3;
-use std::vec::Vec;
+use std::{
+ borrow::Borrow,
+ sync::{Arc, Mutex, RwLock},
+ vec::Vec,
+};
-use futures::{select, stream::FuturesUnordered, stream::StreamExt};
use geometry::Hittable;
use minifb::{Key, Window, WindowOptions};
+use rayon::prelude::*;
use crate::camera::Camera;
+use crate::error::TracerError;
use crate::geometry::sphere::Sphere;
use crate::image::Image;
use crate::ray::Ray;
@@ -22,41 +27,66 @@ use crate::scene::Scene;
use crate::util::random_double;
use crate::vec3::Vec3;
-fn ray_color(scene: &Scene, ray: &Ray) -> Vec3 {
+fn ray_color(scene: &dyn Hittable, ray: &Ray) -> Vec3 {
if let Some(hit_record) = scene.hit(ray, 0.0, std::f64::INFINITY) {
//return hit_record.color;
return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0));
}
+ // TODO: make sky part of scene.
// Sky
let unit_direction = ray.direction().unit_vector();
let t = 0.5 * (unit_direction.y() + 1.0);
(1.0 - t) * Vec3::new(1.0, 1.0, 1.0) + t * Vec3::new(0.5, 0.7, 1.0)
}
-// TODO: Rustify
-async fn raytrace(
- scene: Scene,
- camera: Camera,
- image: Image,
- row: usize,
-) -> Result<(usize, Vec<u32>), error::TracerError> {
- let mut buffer: Vec<u32> = vec![0; image.width as usize];
+fn raytrace(scene: &dyn Hittable, camera: &Camera, image: &Image, row: usize) -> Vec<u32> {
let mut colors: Vec<Vec3> = vec![Vec3::default(); image.width as usize];
-
- for i in 0..buffer.len() {
+ colors.iter_mut().enumerate().for_each(|(i, col)| {
for _ in 0..image.samples_per_pixel {
let u: f64 = (i as f64 + random_double()) / (image.width - 1) as f64;
let v: f64 = (row as f64 + random_double()) / (image.height - 1) as f64;
- colors[i] += ray_color(&scene, &camera.get_ray(u, v));
+ *col += ray_color(scene, &camera.get_ray(u, v));
}
- }
+ });
+ // TODO: Could do rolling average
+ let mut buffer: Vec<u32> = vec![0; image.width as usize];
for i in 0..image.width {
buffer[i] = (colors[i] / image.samples_per_pixel as f64).as_color();
}
- Ok((row, buffer))
+ buffer
+}
+
+type Data = (Arc<RwLock<Vec<u32>>>, Arc<Camera>, Arc<Image>, usize);
+
+fn render(
+ buffer: Arc<RwLock<Vec<u32>>>,
+ camera: Arc<Camera>,
+ image: Arc<Image>,
+ scene: Box<dyn Hittable + std::marker::Sync>,
+) {
+ let scene: &(dyn Hittable + Sync) = scene.borrow();
+
+ let v: Vec<Data> = (0..image.height)
+ .map(|row| {
+ (
+ Arc::clone(&buffer),
+ Arc::clone(&camera),
+ Arc::clone(&image),
+ row,
+ )
+ })
+ .collect();
+
+ v.par_iter().for_each(|(buf, camera, image, row)| {
+ let start = row * image.width;
+ let end = start + image.width;
+ let col_buf = raytrace(scene.borrow(), camera, image, *row);
+ let mut buf = buf.write().expect("Failed to get screen buffer lock."); // TODO: No except
+ buf[start..end].copy_from_slice(col_buf.as_slice());
+ });
}
fn create_scene() -> Scene {
@@ -72,70 +102,67 @@ fn create_scene() -> Scene {
scene
}
-async fn run(
- rows_per_update: u32,
- aspect_ratio: f64,
- screen_width: usize,
- samples: usize,
-) -> Result<(), error::TracerError> {
- let image = image::Image::new(aspect_ratio, screen_width, samples);
- let camera = camera::Camera::new(&image, 2.0, 1.0);
- let scene = create_scene();
-
- let mut screen_buffer: Vec<u32> = vec![0; image.width * image.height];
- let mut window = Window::new(
- "racer-tracer",
- image.width,
- image.height,
- WindowOptions::default(),
- )
- .expect("Unable to create window");
- window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
-
- let mut futs = FuturesUnordered::new();
- // One future per row is a bit high.
- // Could do something less spammy.
- for h in 0..image.height {
- // TODO: Either clone all or lock em all.
- futs.push(raytrace(scene.clone(), camera.clone(), image.clone(), h));
- }
-
- // TODO: use rayon
- // Since it's cooperative multitasking this is not really helpful at the moment.
- // You will get pretty much get the same result without the tokio asyncness.
- // using rayon with threads is a different matter.
- let mut complete = false;
- while window.is_open() && !window.is_key_down(Key::Escape) {
- if !complete {
- for _ in 0..rows_per_update {
- select! {
- res = futs.select_next_some() => {
- let row_buffer = res.expect("Expected to get data");
- let start = row_buffer.0 * image.width;
- let end = start + image.width;
- screen_buffer[start..end].copy_from_slice(row_buffer.1.as_slice());
- },
- complete => {
- if !complete {
- println!("Completed!");
- }
- complete = true;
- },
+fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), TracerError> {
+ let image = Arc::new(image::Image::new(aspect_ratio, screen_width, samples));
+ let camera = Arc::new(camera::Camera::new(&image, 2.0, 1.0));
+ let scene: Box<dyn Hittable + Sync + Send> = Box::new(create_scene());
+ let screen_buffer: Arc<RwLock<Vec<u32>>> =
+ Arc::new(RwLock::new(vec![0; image.width * image.height]));
+
+ let window_res: Arc<Mutex<Result<(), TracerError>>> = Arc::new(Mutex::new(Ok(())));
+
+ rayon::scope(|s| {
+ s.spawn(|_| {
+ render(
+ Arc::clone(&screen_buffer),
+ camera,
+ Arc::clone(&image),
+ scene,
+ )
+ });
+ s.spawn(|_| {
+ let result = Window::new(
+ "racer-tracer",
+ image.width,
+ image.height,
+ WindowOptions::default(),
+ )
+ .map_err(|e| TracerError::FailedToCreateWindow(e.to_string()))
+ .map(|mut window| {
+ window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
+ window
+ })
+ .and_then(|mut window| {
+ // TODO: Only re-render window then buffer is changed
+ while window.is_open() && !window.is_key_down(Key::Escape) {
+ // Sleep a bit to not hog the lock on the buffer all the time.
+ std::thread::sleep(std::time::Duration::from_secs(1));
+
+ screen_buffer
+ .read()
+ .map_err(|e| TracerError::FailedToUpdateWindow(e.to_string()))
+ .and_then(|buf| {
+ window
+ .update_with_buffer(&buf, image.width, image.height)
+ .map_err(|e| TracerError::FailedToUpdateWindow(e.to_string()))
+ })?
}
- }
- }
+ Ok(())
+ });
- window
- .update_with_buffer(&screen_buffer, image.width, image.height)
- .map_err(|e| error::TracerError::FailedToUpdateWindow(e.to_string()))?;
- }
+ if result.is_err() {
+ let mut a = window_res.lock().expect("Failed to get result lock.");
+ *a = result;
+ }
+ });
+ });
- Ok(())
+ let res = (window_res.lock().expect("Failed to get result lock.")).clone();
+ res
}
-#[tokio::main]
-async fn main() {
- if let Err(e) = run(100, 16.0 / 9.0, 1200, 100).await {
+fn main() {
+ if let Err(e) = run(16.0 / 9.0, 1200, 10) {
eprintln!("{}", e);
std::process::exit(e.into())
}
diff --git a/racer-tracer/src/scene.rs b/racer-tracer/src/scene.rs
index 2d530af..672b39b 100644
--- a/racer-tracer/src/scene.rs
+++ b/racer-tracer/src/scene.rs
@@ -1,7 +1,7 @@
use crate::geometry::Hittable;
pub struct Scene {
- objects: Vec<Box<dyn Hittable>>,
+ objects: Vec<Box<dyn Hittable + Sync + Send>>,
}
impl Scene {
@@ -11,27 +11,11 @@ impl Scene {
}
}
- pub fn add(&mut self, hittable: Box<dyn Hittable>) {
+ pub fn add(&mut self, hittable: Box<dyn Hittable + Sync + Send>) {
self.objects.push(hittable);
}
}
-// TODO: What to do?
-// Cloning everything is nice since then every task can do whatever they like.
-// Cloning everything is bad becuse you copy everything which takes time.
-// Could also put locks on the Scene but then it becomes this global object that everyone
-// wants to access at the same time.
-// Will do some laborations later and decide on a solution.
-impl Clone for Scene {
- fn clone(&self) -> Self {
- let mut objects = Vec::with_capacity(self.objects.capacity());
- for i in self.objects.iter() {
- objects.push(i.clone_box());
- }
- Self { objects }
- }
-}
-
impl Hittable for Scene {
fn hit(
&self,
@@ -51,8 +35,4 @@ impl Hittable for Scene {
rec
}
-
- fn clone_box(&self) -> Box<dyn Hittable> {
- Box::new(self.clone())
- }
}