From 3a144c8c1fc83150fc06d792082db5cc4bce3cc5 Mon Sep 17 00:00:00 2001 From: Julian T Date: Tue, 9 Feb 2021 20:00:52 +0100 Subject: Add tiling for rendering --- src/camera/film.rs | 2 +- src/core/bound.rs | 17 +++---- src/core/vector2.rs | 55 +++++++++++++++------- src/lib.rs | 3 +- src/main.rs | 13 +++--- src/render.rs | 48 ------------------- src/render/coordinator.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 5 ++ src/render/task.rs | 49 ++++++++++++++++++++ 9 files changed, 225 insertions(+), 82 deletions(-) delete mode 100644 src/render.rs create mode 100644 src/render/coordinator.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/task.rs diff --git a/src/camera/film.rs b/src/camera/film.rs index 7a7f2dc..852ae9e 100644 --- a/src/camera/film.rs +++ b/src/camera/film.rs @@ -14,7 +14,7 @@ pub struct Pixel { } pub struct Film { - size: Vector2i, + pub size: Vector2i, pub frame: Bound2i, pixels: Vec, diff --git a/src/core/bound.rs b/src/core/bound.rs index a1c1070..404424e 100644 --- a/src/core/bound.rs +++ b/src/core/bound.rs @@ -45,6 +45,15 @@ impl Bound2 { ) } + /// Finds the intersected area between two bounds + pub fn intersect(&self, b: &Bound2) -> Bound2 { + Bound2::new( + &Vector2::new_xy(max(self.min.x, b.min.x), max(self.min.y, b.min.y)), + &Vector2::new_xy(min(self.max.x, b.max.x), min(self.max.y, b.max.y)), + ) + } + + /// Calculates the diagonal vector /// /// Can be used to calculate the size of the bound @@ -96,14 +105,6 @@ impl From<&Bound2f> for Bound2i { } } -/// Finds the intersected area between two bounds -pub fn intersect(a: &Bound2, b: &Bound2) -> Bound2 { - Bound2::new( - &Vector2::new_xy(max(a.min.x, b.min.x), max(a.min.y, b.min.y)), - &Vector2::new_xy(min(a.max.x, b.max.x), min(a.max.y, b.max.y)), - ) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/core/vector2.rs b/src/core/vector2.rs index 7e40394..0e0165e 100644 --- a/src/core/vector2.rs +++ b/src/core/vector2.rs @@ -2,7 +2,7 @@ //! //! This is implemented generictly with types that fit in the Number trait use crate::{Float, Number}; -use std::ops::{Sub, Add, Mul}; +use std::ops::{Sub, Add, Mul, Div}; use std::fmt; use std::cmp::min; @@ -15,6 +15,7 @@ pub struct Vector2 { pub type Vector2f = Vector2; pub type Vector2i = Vector2; + impl Vector2 { pub fn new(initial: T) -> Vector2 { Vector2 { x: initial, y: initial } @@ -45,6 +46,16 @@ impl Add for Vector2 { } } +impl Mul for Vector2 { + type Output = Self; + fn mul(self, op: Self) -> Self::Output { + Self::new_xy( + self.x * op.x, + self.y * op.y, + ) + } +} + impl Mul for Vector2 { type Output = Self; fn mul(self, op: T) -> Self::Output { @@ -55,25 +66,19 @@ impl Mul for Vector2 { } } -impl fmt::Display for Vector2 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_fmt(format_args!("[{}, {}]", self.x, self.y)) - } -} - -impl Vector2f { - pub fn ceil(&self) -> Self { +impl Div for Vector2 { + type Output = Self; + fn div(self, op: Self) -> Self::Output { Self::new_xy( - self.x.ceil(), - self.y.ceil() - ) + self.x / op.x, + self.y / op.y, + ) } +} - pub fn floor(&self) -> Self { - Self::new_xy( - self.x.floor(), - self.y.floor() - ) +impl fmt::Display for Vector2 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("[{}, {}]", self.x, self.y)) } } @@ -96,6 +101,8 @@ impl From for Vector2i { } impl Vector2i { + pub const ZERO: Self = Vector2i {x: 0, y: 0}; + pub fn cap(&self, x: i32, y: i32) -> Self { Self::new_xy( min(self.x, x), @@ -108,6 +115,20 @@ impl Vector2f { pub fn len(&self) -> Float { (self.x*self.x + self.y*self.y).sqrt() } + + pub fn ceil(&self) -> Self { + Self::new_xy( + self.x.ceil(), + self.y.ceil() + ) + } + + pub fn floor(&self) -> Self { + Self::new_xy( + self.x.floor(), + self.y.floor() + ) + } } diff --git a/src/lib.rs b/src/lib.rs index 1593471..f3047d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub mod trace; pub mod sample; pub mod material; -use std::ops::{Add, Sub, Mul, DivAssign, AddAssign, Neg}; +use std::ops::{Add, Sub, Mul, DivAssign, AddAssign, Neg, Div}; use std::cmp; use std::fmt; use std::f64::consts::PI; @@ -21,6 +21,7 @@ pub trait Number: Add + Mul + Neg + + Div + DivAssign + AddAssign + fmt::Display diff --git a/src/main.rs b/src/main.rs index e606cba..2247834 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use rendering::scene::{Scene, Object}; use rendering::trace::DefaultTracer; use rendering::scene::shapes::Sphere; use rendering::core::{Vector2i, Vector3f, Spectrum}; -use rendering::render::{RenderContext, RenderTask}; +use rendering::render::{RenderContext, RenderCoord}; use rendering::sample::UniformSampler; use rendering::material::{Reflectant, Lambertian}; @@ -33,19 +33,18 @@ fn main() { Object::new(brown.clone(), Box::new(Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0)))), ]); - let tracer = DefaultTracer::new(&scn, None); + let tracer = DefaultTracer::new(&scn, Some(50)); let mut sampler = UniformSampler::new(); let ctx = RenderContext { cam: &cam, trc: &tracer }; let mut film = Film::new(res); - let tile = film.get_tile(&film.frame); + { + let coord = RenderCoord::new(&mut film, Vector2i::new_xy(32, 32), 100); - let mut task = RenderTask::new(Box::new(tile), 100); - task.render(&ctx, &mut sampler); - - film.commit_tile(&task.tile); + coord.work(&ctx, &mut sampler); + } let image = film.finalize_image(); if let Err(e) = image.save("test.png") { diff --git a/src/render.rs b/src/render.rs deleted file mode 100644 index 9a6b905..0000000 --- a/src/render.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Implements the main render loop -//! -//! This is not a final design -use crate::camera::film::FilmTile; -use crate::camera::Camera; -use crate::trace::{DefaultTracer, Tracer}; -use crate::sample::Sampler; - -use crate::core::{Vector2f}; -use crate::Float; - -pub struct RenderTask { - pub tile: Box, - samples: u32, -} - -pub struct RenderContext<'a> { - pub cam: &'a Camera, - pub trc: &'a DefaultTracer<'a>, -} - -impl RenderTask { - pub fn new(tile: Box, samples: u32) -> Self { - Self { tile, samples } - } - - fn render_at(&mut self, ctx: &RenderContext, x: i32, y: i32, sampler: &mut dyn Sampler) { - let corner = Vector2f::new_xy(x as Float, y as Float); - - for _ in 0..self.samples { - let p = corner + sampler.get_sample_2d(); - - // Create a ray - let (r, _) = ctx.cam.generate_ray(&p, sampler); - - self.tile.add_sample(&p, ctx.trc.trace(sampler, &r)); - } - } - - pub fn render(&mut self, ctx: &RenderContext, sampler: &mut dyn Sampler) { - let b = self.tile.bounds.clone(); - for y in b.min.y .. b.max.y { - for x in b.min.x .. b.max.x { - self.render_at(ctx, x, y, sampler); - } - } - } -} diff --git a/src/render/coordinator.rs b/src/render/coordinator.rs new file mode 100644 index 0000000..a9a7d06 --- /dev/null +++ b/src/render/coordinator.rs @@ -0,0 +1,115 @@ +//! Split a image into small tiles +//! +//! This enables multithreaded rendering. + +use super::{RenderTask, RenderContext}; +use crate::camera::Film; +use crate::core::{Bound2i, Vector2i}; +use crate::sample::Sampler; + +use std::sync::Mutex; + +struct Tiler { + tilesize: Vector2i, + + fullbound: Bound2i, + tilemap_size: Vector2i, + tilemap_area: i32, + + next_tile: i32, + tiles_done: i32, +} + +pub struct RenderCoord<'a> { + film: Mutex<&'a mut Film>, + samples: u32, + tiler: Mutex, +} + +impl Tiler { + pub fn new(fullsize: Vector2i, tilesize: Vector2i) -> Tiler { + assert!(tilesize.x != 0 && tilesize.y != 0); + + let tilemap_size = fullsize / tilesize; + + Self { + tilesize, + + fullbound: Bound2i::new(&Vector2i::ZERO, &fullsize), + tilemap_size, + tilemap_area: (tilemap_size.x * tilemap_size.y), + + next_tile: 0, + tiles_done: 0, + } + + } + + pub fn next_tile(&mut self) -> Option<(Bound2i, i32)> { + // Check if we are outside + if self.next_tile >= self.tilemap_area { + return None; + } + + // Convert the tile to xy in tilemap + let tile = Vector2i::new_xy(self.next_tile / self.tilemap_size.x, self.next_tile % self.tilemap_size.x); + let tile_index = self.next_tile; + + self.next_tile += 1; + + // Create a bound from the tilecoordinate + let tile_start = tile * self.tilesize; + // We need to make sure the resulting tile is inside the image bound + Some(( + Bound2i::new(&tile_start, &(tile_start + self.tilesize)).intersect(&self.fullbound), + tile_index, + )) + } + + /// Mark a index as done and return the overall process + pub fn mark_done(&mut self, _index: i32) -> f32 { + self.tiles_done += 1; + + println!("Progress: {}/{}", self.tiles_done, self.tilemap_area); + self.tiles_done as f32 / self.tilemap_area as f32 + } +} + +impl<'a> RenderCoord<'a> { + pub fn new(film: &'a mut Film, tilesize: Vector2i, samples: u32) -> Self { + let size = film.size; + + Self { + film: Mutex::new(film), + samples, + tiler: Mutex::new(Tiler::new(size, tilesize)), + } + } + + pub fn next_task(&self) -> Option { + let (tile_bound, index) = self.tiler.lock().unwrap().next_tile()?; + + let film_tile = { + let film = self.film.lock().unwrap(); + Box::new(film.get_tile(&tile_bound)) + }; + + Some(RenderTask::new(film_tile, self.samples, index)) + } + + pub fn finish_task(&self, task: &RenderTask) { + { + let mut film = self.film.lock().unwrap(); + film.commit_tile(task.tile.as_ref()); + } + + self.tiler.lock().unwrap().mark_done(task.tile_index); + } + + pub fn work(&self, ctx: &RenderContext, sampler: &mut dyn Sampler) { + while let Some(mut task) = self.next_task() { + task.render(ctx, sampler); + self.finish_task(&task); + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..b81aaf5 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,5 @@ +mod task; +mod coordinator; + +pub use task::{RenderContext, RenderTask}; +pub use coordinator::RenderCoord; diff --git a/src/render/task.rs b/src/render/task.rs new file mode 100644 index 0000000..017fe24 --- /dev/null +++ b/src/render/task.rs @@ -0,0 +1,49 @@ +//! Implements the main render loop +//! +//! This is not a final design +use crate::camera::film::FilmTile; +use crate::camera::Camera; +use crate::trace::{DefaultTracer, Tracer}; +use crate::sample::Sampler; + +use crate::core::{Vector2f}; +use crate::Float; + +pub struct RenderTask { + pub tile: Box, + samples: u32, + pub tile_index: i32, +} + +pub struct RenderContext<'a> { + pub cam: &'a Camera, + pub trc: &'a DefaultTracer<'a>, +} + +impl RenderTask { + pub fn new(tile: Box, samples: u32, tile_index: i32) -> Self { + Self { tile, samples, tile_index } + } + + fn render_at(&mut self, ctx: &RenderContext, x: i32, y: i32, sampler: &mut dyn Sampler) { + let corner = Vector2f::new_xy(x as Float, y as Float); + + for _ in 0..self.samples { + let p = corner + sampler.get_sample_2d(); + + // Create a ray + let (r, _) = ctx.cam.generate_ray(&p, sampler); + + self.tile.add_sample(&p, ctx.trc.trace(sampler, &r)); + } + } + + pub fn render(&mut self, ctx: &RenderContext, sampler: &mut dyn Sampler) { + let b = self.tile.bounds.clone(); + for y in b.min.y .. b.max.y { + for x in b.min.x .. b.max.x { + self.render_at(ctx, x, y, sampler); + } + } + } +} -- cgit v1.2.3