diff options
| author | Sakarias Johansson <sakarias.johansson@goodbyekansas.com> | 2023-04-05 21:38:33 +0200 |
|---|---|---|
| committer | Sakarias Johansson <sakariasjohansson@hotmail.com> | 2023-04-05 22:06:03 +0200 |
| commit | 5f8faa17457426c4ca8c54bf67b5aa96eb7a52ea (patch) | |
| tree | fd2972858804a93294e4ad95057b19c17340acbb /racer-tracer/src | |
| parent | 71ea29e48bdfc500a4e958cc1f37eddbd1d7bd2c (diff) | |
| download | racer-tracer-5f8faa17457426c4ca8c54bf67b5aa96eb7a52ea.tar.gz racer-tracer-5f8faa17457426c4ca8c54bf67b5aa96eb7a52ea.tar.xz racer-tracer-5f8faa17457426c4ca8c54bf67b5aa96eb7a52ea.zip | |
🎨 Move render to rendering interface
Removed render.rs and added a renderer trait.
Planning on making several implementations so it makes sense to move
it to a trait so you can change implementations.
Diffstat (limited to 'racer-tracer/src')
| -rw-r--r-- | racer-tracer/src/config.rs | 6 | ||||
| -rw-r--r-- | racer-tracer/src/main.rs | 77 | ||||
| -rw-r--r-- | racer-tracer/src/render.rs | 247 | ||||
| -rw-r--r-- | racer-tracer/src/renderer.rs | 51 | ||||
| -rw-r--r-- | racer-tracer/src/renderer/cpu.rs | 124 | ||||
| -rw-r--r-- | racer-tracer/src/renderer/cpu_scaled.rs | 106 |
6 files changed, 325 insertions, 286 deletions
diff --git a/racer-tracer/src/config.rs b/racer-tracer/src/config.rs index 4cb0880..3992176 100644 --- a/racer-tracer/src/config.rs +++ b/racer-tracer/src/config.rs @@ -13,7 +13,7 @@ pub struct Screen { } #[derive(Default, Debug, Deserialize)] -pub struct RenderData { +pub struct RenderConfigData { pub samples: usize, pub max_depth: usize, pub num_threads_width: usize, @@ -108,10 +108,10 @@ impl FromStr for ImageAction { #[derive(Default, Debug, Deserialize)] pub struct Config { #[serde(default)] - pub preview: RenderData, + pub preview: RenderConfigData, #[serde(default)] - pub render: RenderData, + pub render: RenderConfigData, #[serde(default)] pub screen: Screen, diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs index b70a140..f950000 100644 --- a/racer-tracer/src/main.rs +++ b/racer-tracer/src/main.rs @@ -8,7 +8,7 @@ mod image_action; mod key_inputs; mod material; mod ray; -mod render; +mod renderer; mod scene; mod terminal; mod util; @@ -36,7 +36,10 @@ use structopt::StructOpt; use synchronoise::SignalEvent; use terminal::Terminal; -use crate::vec3::Vec3; +use crate::{ + renderer::{RenderData, Renderer}, + vec3::Vec3, +}; use crate::{ camera::Camera, @@ -44,13 +47,14 @@ use crate::{ error::TracerError, image_action::ImageAction, key_inputs::KeyInputs, - render::render, + renderer::{cpu::CpuRenderer, cpu_scaled::CpuRendererScaled}, scene::Scene, }; fn run(config: Config, log: Logger, term: Terminal) -> Result<(), TracerError> { info!(log, "Starting racer-tracer {}", env!("CARGO_PKG_VERSION")); - + let renderer: &dyn Renderer = &CpuRenderer {} as &dyn Renderer; + let renderer_preview: &dyn Renderer = &CpuRendererScaled {} as &dyn Renderer; let image = image::Image::new(config.screen.width, config.screen.height); let screen_buffer: RwLock<Vec<u32>> = RwLock::new(vec![0; image.width * image.height]); let look_from = Vec3::new(13.0, 2.0, 3.0); @@ -138,42 +142,43 @@ fn run(config: Config, log: Logger, term: Terminal) -> Result<(), TracerError> { || { render_image.reset(); // Render preview - render( - &screen_buffer, - &camera, - &image, - &scene, - &config.preview, - None, - Some(config.preview.scale), - ) + renderer_preview.render(RenderData { + buffer: &screen_buffer, + camera: &camera, + image: &image, + scene: &scene, + config: &config, + cancel_event: None, + }) }, |_| { render_image.reset(); let render_time = Instant::now(); - render( - &screen_buffer, - &camera, - &image, - &scene, - &config.render, - Some(&render_image), - None, - ) - .and_then(|_| { - info!( - log, - "It took {} seconds to render the image.", - Instant::now().duration_since(render_time).as_secs() - ); - image_action.action( - &screen_buffer, - &render_image, - &config, - log.new(o!("scope" => "image-action")), - &term, - ) - }) + renderer + .render(RenderData { + buffer: &screen_buffer, + camera: &camera, + image: &image, + scene: &scene, + config: &config, + cancel_event: Some(&render_image), + }) + .and_then(|_| { + info!( + log, + "It took {} seconds to render the image.", + Instant::now() + .duration_since(render_time) + .as_secs() + ); + image_action.action( + &screen_buffer, + &render_image, + &config, + log.new(o!("scope" => "image-action")), + &term, + ) + }) }, ) }); diff --git a/racer-tracer/src/render.rs b/racer-tracer/src/render.rs deleted file mode 100644 index 6926091..0000000 --- a/racer-tracer/src/render.rs +++ /dev/null @@ -1,247 +0,0 @@ -use std::{sync::RwLock, time::Duration}; - -use rayon::prelude::*; -use synchronoise::SignalEvent; - -use crate::{ - camera::Camera, - config::RenderData, - error::TracerError, - geometry::Hittable, - image::{Image, SubImage}, - ray::Ray, - util::random_double, - vec3::{Color, Vec3}, -}; - -fn ray_color(scene: &dyn Hittable, ray: &Ray, depth: usize) -> Vec3 { - if depth == 0 { - return Vec3::default(); - } - - if let Some(rec) = scene.hit(ray, 0.001, std::f64::INFINITY) { - if let Some((scattered, attenuation)) = rec.material.scatter(ray, &rec) { - return attenuation * ray_color(scene, &scattered, depth - 1); - } - return Color::default(); - } - - // Sky - let first_color = Vec3::new(1.0, 1.0, 1.0); - let second_color = Vec3::new(0.5, 0.7, 1.0); - let unit_direction = ray.direction().unit_vector(); - let t = 0.5 * (unit_direction.y() + 1.0); - (1.0 - t) * first_color + t * second_color -} - -fn do_cancel(cancel_event: Option<&SignalEvent>) -> bool { - match cancel_event { - Some(event) => event.wait_timeout(Duration::from_secs(0)), - None => false, - } -} - -fn get_highest_divdable(value: usize, mut div: usize) -> usize { - // Feels like there could possibly be some other nicer trick to this. - while (value % div) != 0 { - div -= 1; - } - div -} - -pub fn raytrace_scaled( - buffer: &RwLock<Vec<u32>>, - cancel_event: Option<&SignalEvent>, - scene: &dyn Hittable, - camera: Camera, - image: &SubImage, - data: &RenderData, - scale: (usize, usize), -) -> Result<(), TracerError> { - let (scale_width, scale_height) = scale; - let scaled_width = image.width / scale_width; - let scaled_height = image.height / scale_height; - - let mut colors: Vec<Vec3> = vec![Vec3::default(); scaled_height * scaled_width]; - for row in 0..scaled_height { - for column in 0..scaled_width { - let u: f64 = ((image.x + column * scale_width) as f64 + random_double()) - / (image.screen_width - 1) as f64; - for _ in 0..data.samples { - let v: f64 = ((image.y + row * scale_height) as f64 + random_double()) - / (image.screen_height - 1) as f64; - colors[row * scaled_width + column].add(ray_color( - scene, - &camera.get_ray(u, v), - data.max_depth, - )); - } - } - - if do_cancel(cancel_event) { - return Ok(()); - } - } - - (!do_cancel(cancel_event)) - .then_some(|| ()) - .ok_or(TracerError::CancelEvent) - .and_then(|_| { - buffer - .write() - .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) - }) - .map(|mut buf| { - let offset = image.y * image.screen_width + image.x; - for scaled_row in 0..scaled_height { - for scaled_col in 0..scaled_width { - let color = colors[scaled_row * scaled_width + scaled_col] - .scale_sqrt(data.samples) - .as_color(); - let row = scaled_row * scale_height; - let col = scaled_col * scale_width; - for scale_h in 0..scale_height { - for scale_w in 0..scale_width { - buf[offset + (row + scale_h) * image.screen_width + col + scale_w] = - color; - } - } - } - } - }) -} - -pub fn raytrace( - buffer: &RwLock<Vec<u32>>, - cancel_event: Option<&SignalEvent>, - scene: &dyn Hittable, - camera: Camera, - image: &SubImage, - data: &RenderData, -) -> Result<(), TracerError> { - let mut colors: Vec<Vec3> = vec![Vec3::default(); image.height * image.width]; - for row in 0..image.height { - for column in 0..image.width { - let u: f64 = - ((image.x + column) as f64 + random_double()) / (image.screen_width - 1) as f64; - for _ in 0..data.samples { - let v: f64 = - ((image.y + row) as f64 + random_double()) / (image.screen_height - 1) as f64; - colors[row * image.width + column].add(ray_color( - scene, - &camera.get_ray(u, v), - data.max_depth, - )); - } - } - - if do_cancel(cancel_event) { - return Ok(()); - } - } - - (!do_cancel(cancel_event)) - .then_some(|| ()) - .ok_or(TracerError::CancelEvent) - .and_then(|_| { - buffer - .write() - .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) - }) - .map(|mut buf| { - let offset = image.y * image.screen_width + image.x; - for row in 0..image.height { - for col in 0..image.width { - let color = colors[row * image.width + col] - .scale_sqrt(data.samples) - .as_color(); - buf[offset + row * image.screen_width + col] = color; - } - } - }) -} - -pub fn render( - buffer: &RwLock<Vec<u32>>, - camera: &RwLock<Camera>, - image: &Image, - scene: &dyn Hittable, - data: &RenderData, - cancel_event: Option<&SignalEvent>, - scale: Option<usize>, -) -> Result<(), TracerError> { - if do_cancel(cancel_event) { - return Ok(()); - } - - let width_step = image.width / data.num_threads_width; - let height_step = image.height / data.num_threads_height; - let scaled_width = scale.map_or(1, |s| get_highest_divdable(width_step, s)); - let scaled_height = scale.map_or(1, |s| get_highest_divdable(height_step, s)); - - (!do_cancel(cancel_event)) - .then_some(|| ()) - .ok_or(TracerError::CancelEvent) - .and_then(|_| { - camera - .read() - .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) - // We make a clone of it as it's not very important to - // have the latest camera angle etc. Better to keep - // the lock to a minimum. - .map(|cam| cam.clone()) - }) - .map(|cam| { - let images = (0..data.num_threads_width) - .flat_map(|ws| { - (0..data.num_threads_height) - .map(|hs| SubImage { - x: width_step * ws, - y: height_step * hs, - screen_width: image.width, - screen_height: image.height, - - // Neccesary in case the threads width is not - // evenly divisible by the image width. - width: if ws == data.num_threads_width - 1 { - image.width - width_step * ws - } else { - width_step - }, - - // Neccesary in case the threads height is not - // evenly divisible by the image height. - height: if hs == data.num_threads_height - 1 { - image.height - height_step * hs - } else { - height_step - }, - }) - .collect::<Vec<SubImage>>() - }) - .collect::<Vec<SubImage>>(); - - (cam, images) - }) - .and_then(|(cam, sub_images)| { - sub_images - .into_par_iter() - .map(|image| { - scale.map_or_else( - || raytrace(buffer, cancel_event, scene, cam.clone(), &image, data), - |_| { - raytrace_scaled( - buffer, - cancel_event, - scene, - cam.clone(), - &image, - data, - (scaled_width, scaled_height), - ) - }, - ) - }) - .collect::<Result<(), TracerError>>() - }) -} diff --git a/racer-tracer/src/renderer.rs b/racer-tracer/src/renderer.rs new file mode 100644 index 0000000..162dc4b --- /dev/null +++ b/racer-tracer/src/renderer.rs @@ -0,0 +1,51 @@ +use std::{sync::RwLock, time::Duration}; + +use synchronoise::SignalEvent; + +use crate::{ + camera::Camera, config::Config, error::TracerError, geometry::Hittable, image::Image, ray::Ray, + vec3::Color, vec3::Vec3, +}; + +pub mod cpu; +pub mod cpu_scaled; + +fn do_cancel(cancel_event: Option<&SignalEvent>) -> bool { + match cancel_event { + Some(event) => event.wait_timeout(Duration::from_secs(0)), + None => false, + } +} + +fn ray_color(scene: &dyn Hittable, ray: &Ray, depth: usize) -> Vec3 { + if depth == 0 { + return Vec3::default(); + } + + if let Some(rec) = scene.hit(ray, 0.001, std::f64::INFINITY) { + if let Some((scattered, attenuation)) = rec.material.scatter(ray, &rec) { + return attenuation * ray_color(scene, &scattered, depth - 1); + } + return Color::default(); + } + + // Sky + let first_color = Vec3::new(1.0, 1.0, 1.0); + let second_color = Vec3::new(0.5, 0.7, 1.0); + let unit_direction = ray.direction().unit_vector(); + let t = 0.5 * (unit_direction.y() + 1.0); + (1.0 - t) * first_color + t * second_color +} + +pub struct RenderData<'a> { + pub buffer: &'a RwLock<Vec<u32>>, + pub camera: &'a RwLock<Camera>, + pub image: &'a Image, + pub scene: &'a dyn Hittable, + pub config: &'a Config, + pub cancel_event: Option<&'a SignalEvent>, +} + +pub trait Renderer: Send + Sync { + fn render(&self, render_data: RenderData) -> Result<(), TracerError>; +} diff --git a/racer-tracer/src/renderer/cpu.rs b/racer-tracer/src/renderer/cpu.rs new file mode 100644 index 0000000..f97ad9c --- /dev/null +++ b/racer-tracer/src/renderer/cpu.rs @@ -0,0 +1,124 @@ +use rayon::prelude::*; + +use crate::{ + camera::Camera, + error::TracerError, + image::SubImage, + renderer::{do_cancel, ray_color, Renderer}, + util::random_double, + vec3::Vec3, +}; + +use super::RenderData; + +pub struct CpuRenderer {} + +impl CpuRenderer { + pub fn raytrace( + &self, + rd: &RenderData, + camera: &Camera, + image: &SubImage, + ) -> Result<(), TracerError> { + let mut colors: Vec<Vec3> = vec![Vec3::default(); image.height * image.width]; + for row in 0..image.height { + for column in 0..image.width { + let u: f64 = + ((image.x + column) as f64 + random_double()) / (image.screen_width - 1) as f64; + for _ in 0..rd.config.render.samples { + let v: f64 = ((image.y + row) as f64 + random_double()) + / (image.screen_height - 1) as f64; + colors[row * image.width + column].add(ray_color( + rd.scene, + &camera.get_ray(u, v), + rd.config.render.max_depth, + )); + } + } + + if do_cancel(rd.cancel_event) { + return Ok(()); + } + } + + (!do_cancel(rd.cancel_event)) + .then_some(|| ()) + .ok_or(TracerError::CancelEvent) + .and_then(|_| { + rd.buffer + .write() + .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) + }) + .map(|mut buf| { + let offset = image.y * image.screen_width + image.x; + for row in 0..image.height { + for col in 0..image.width { + let color = colors[row * image.width + col] + .scale_sqrt(rd.config.render.samples) + .as_color(); + buf[offset + row * image.screen_width + col] = color; + } + } + }) + } + + pub fn prepare_threads(rd: &RenderData) -> Result<(Camera, Vec<SubImage>), TracerError> { + let width_step = rd.image.width / rd.config.render.num_threads_width; + let height_step = rd.image.height / rd.config.render.num_threads_height; + + (!do_cancel(rd.cancel_event)) + .then_some(|| ()) + .ok_or(TracerError::CancelEvent) + .and_then(|_| { + rd.camera + .read() + .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) + // We make a clone of it as we don't want the + // camera to move as we render the scene. It also + // ensures we keep the lock to a minimum. + .map(|cam| cam.clone()) + }) + .map(|cam| { + let images = (0..rd.config.render.num_threads_width) + .flat_map(|ws| { + (0..rd.config.render.num_threads_height) + .map(|hs| SubImage { + x: width_step * ws, + y: height_step * hs, + screen_width: rd.image.width, + screen_height: rd.image.height, + + // Neccesary in case the threads width is not + // evenly divisible by the image width. + width: if ws == rd.config.render.num_threads_width - 1 { + rd.image.width - width_step * ws + } else { + width_step + }, + + // Neccesary in case the threads height is not + // evenly divisible by the image height. + height: if hs == rd.config.render.num_threads_height - 1 { + rd.image.height - height_step * hs + } else { + height_step + }, + }) + .collect::<Vec<SubImage>>() + }) + .collect::<Vec<SubImage>>(); + (cam, images) + }) + } +} + +impl Renderer for CpuRenderer { + fn render(&self, rd: RenderData) -> Result<(), TracerError> { + CpuRenderer::prepare_threads(&rd).and_then(|(cam, images)| { + images + .into_par_iter() + .map(|image| self.raytrace(&rd, &cam, &image)) + .collect::<Result<(), TracerError>>() + }) + } +} diff --git a/racer-tracer/src/renderer/cpu_scaled.rs b/racer-tracer/src/renderer/cpu_scaled.rs new file mode 100644 index 0000000..44e73fa --- /dev/null +++ b/racer-tracer/src/renderer/cpu_scaled.rs @@ -0,0 +1,106 @@ +use rayon::prelude::*; + +use crate::{ + camera::Camera, + error::TracerError, + image::SubImage, + renderer::{cpu::CpuRenderer, do_cancel, ray_color, Renderer}, + util::random_double, + vec3::Vec3, +}; + +use super::RenderData; + +fn get_highest_divdable(value: usize, mut div: usize) -> usize { + // Feels like there could possibly be some other nicer trick to this. + while (value % div) != 0 { + div -= 1; + } + div +} + +pub struct CpuRendererScaled {} + +impl CpuRendererScaled { + pub fn raytrace( + &self, + rd: &RenderData, + camera: &Camera, + image: &SubImage, + scale: (usize, usize), + ) -> Result<(), TracerError> { + let (scale_width, scale_height) = scale; + let scaled_width = image.width / scale_width; + let scaled_height = image.height / scale_height; + + let mut colors: Vec<Vec3> = vec![Vec3::default(); scaled_height * scaled_width]; + for row in 0..scaled_height { + for column in 0..scaled_width { + let u: f64 = ((image.x + column * scale_width) as f64 + random_double()) + / (image.screen_width - 1) as f64; + for _ in 0..rd.config.preview.samples { + let v: f64 = ((image.y + row * scale_height) as f64 + random_double()) + / (image.screen_height - 1) as f64; + colors[row * scaled_width + column].add(ray_color( + rd.scene, + &camera.get_ray(u, v), + rd.config.preview.max_depth, + )); + } + } + + if do_cancel(rd.cancel_event) { + return Ok(()); + } + } + + (!do_cancel(rd.cancel_event)) + .then_some(|| ()) + .ok_or(TracerError::CancelEvent) + .and_then(|_| { + rd.buffer + .write() + .map_err(|e| TracerError::FailedToAcquireLock(e.to_string())) + }) + .map(|mut buf| { + let offset = image.y * image.screen_width + image.x; + for scaled_row in 0..scaled_height { + for scaled_col in 0..scaled_width { + let color = colors[scaled_row * scaled_width + scaled_col] + .scale_sqrt(rd.config.preview.samples) + .as_color(); + let row = scaled_row * scale_height; + let col = scaled_col * scale_width; + for scale_h in 0..scale_height { + for scale_w in 0..scale_width { + buf[offset + + (row + scale_h) * image.screen_width + + col + + scale_w] = color; + } + } + } + } + }) + } +} + +impl Renderer for CpuRendererScaled { + fn render(&self, rd: RenderData) -> Result<(), crate::error::TracerError> { + let scale_width = get_highest_divdable( + rd.image.width / rd.config.render.num_threads_width, + rd.config.preview.scale, + ); + let scale_height = get_highest_divdable( + rd.image.height / rd.config.render.num_threads_height, + rd.config.preview.scale, + ); + + CpuRenderer::prepare_threads(&rd).and_then(|(cam, images)| { + images + .into_par_iter() + .map(|image| self.raytrace(&rd, &cam, &image, (scale_width, scale_height))) + .collect::<Result<(), TracerError>>() + }) + } +} |
