From 3250f8e580285bd4969088851a112b8fe860090d Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Mon, 25 Sep 2023 10:50:47 -0700 Subject: [PATCH] Hook up the new renderer The threading code is gone, now. We're back to just having a single loop to drive the whole thing. Along with this, I realized that the Distrs container thing wasn't actually being used. It's a real pain to cart it around, and very few things actually use it. TODO: Reinstate the small wiggle done by the uv mapping routine. This version no longer nudges the coordinate, so I expect there to be some small visual differences. --- src/main.rs | 119 ++++++++++++++---------------------------------- src/renderer.rs | 35 ++++++-------- 2 files changed, 48 insertions(+), 106 deletions(-) diff --git a/src/main.rs b/src/main.rs index bc8e645..3c0bc97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,26 +3,37 @@ mod primitives; mod scene; mod renderer; -use crate::primitives::Vec3; +use crate::primitives::{ + Vec2i, + Vec3, + Rect, +}; use crate::scene::{ Camera, Scene }; -use crate::renderer::RenderCommand; + +use crate::renderer::{ + Tile, + RenderProperties, +}; use rand::SeedableRng; use rand::rngs::SmallRng; -use std::thread; + fn main() { // image let aspect_ratio = 3.0 / 2.0; - let image = ( - 400, - (400.0 / aspect_ratio) as i32 - ); - let samples_per_pixel: u32 = 10; - let max_depth = 50; + let image = Vec2i { + x: 400, + y: (400.0 / aspect_ratio) as i32 + }; + + let render_config = RenderProperties { + samples: 10, + bounces: 50 + }; // random generator let mut small_rng = SmallRng::seed_from_u64(0); @@ -44,84 +55,20 @@ fn main() { // render // The render loop should now be a job submission mechanism // Iterate lines, submitting them as tasks to the thread. - println!("P3\n{} {}\n255", image.0, image.1); - let context = renderer::RenderContext { - camera: &scene.camera, - image, - max_depth, - samples_per_pixel, - world: scene.world, - }; - - thread::scope(|s| { - let (mut dispatcher, scanline_receiver) = renderer::Dispatcher::new(&small_rng, 12); - - s.spawn(move || { - for y in (0..image.1).rev() { - eprintln!("Submitting scanline: {}", y); - let job = RenderCommand::Line { line_num: y, context: context.clone() }; - dispatcher.submit_job(job).unwrap(); - } - - dispatcher.submit_job(RenderCommand::Stop).unwrap(); - // ... also I happen to know there are 4 threads. - }); - - /* - * Store received results in the segments buffer. - * Some will land before their previous segments and will need to be held - * until the next-to-write arrives. - * - * Elements are sorted in reverse order so that they can be popped from the - * Vec quickly. - * - * The queue is scanned every single time a new item is received. In the - * happy path where the received item is next-up, it'll be buffered, checked - * and then printed. In the case where it isn't, it'll get buffered and - * stick around for more loops. When the next-to-write finally lands, it - * means the n+1 element is up, now. If that element is already in the buffer - * we want to write it out. Hence the loop that scans the whole buffer each - * receive. - * - * TODO: There could be an up-front conditional that checks to see if the - * received item *is* the next-to-write and skip the buffering step. - * But I need to make the concept work at all, first. - */ - let mut raster_segments = Vec::::new(); - let mut sl_output_index = image.1-1; // scanlines count down, start at image height. - while let Ok(scanline) = scanline_receiver.recv() { - eprintln!("Received scanline: {}", scanline.line_num); - - raster_segments.push(scanline); - raster_segments.sort_by( |a, b| b.cmp(a) ); - - loop { - if raster_segments.len() == 0 { break; } // can this ever happen? Not while every - // single element gets pushed to the - // buffer first. With the happy path - // short-circuit noted above, it could. - - let last_ind = raster_segments.len() - 1; - if raster_segments[last_ind].line_num == sl_output_index{ - let scanline = raster_segments.pop().unwrap(); - print_scanline(scanline, samples_per_pixel); - sl_output_index -= 1; - } else { - break; - } - } + println!("P3\n{} {}\n255", image.x, image.y); + for row in (0..image.y).rev() { + let bounds = Rect{ // render boundary (a horizontal slice) + x: 0, + y: row, + w: image.x, + h: 1 + }; + let tile = Tile::render_line(bounds, row, image, &scene, &render_config, &mut small_rng); + eprintln!("Printing scanline #{}", row); + for pixel in tile.pixels { + println!("{}", pixel.print_ppm(render_config.samples)) } - eprintln!("Size of raster_segments at finish: {}", raster_segments.len()); - }); - - + } // TODO: Dispatcher shutdown mechanism. Right now, we might technically be leaking threads. eprintln!("Done!"); } - -fn print_scanline(scanline: renderer::RenderResult, samples_per_pixel: u32){ - eprintln!("Printing scanline num: {}", scanline.line_num); - for color in &scanline.line { - println!("{}", color.print_ppm(samples_per_pixel)); - } -} diff --git a/src/renderer.rs b/src/renderer.rs index febf774..9c65438 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -12,18 +12,12 @@ use crate::scene::{ }; use rand::rngs::SmallRng; -use rand::distributions::Uniform; const SKY_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.7, z: 1.0}; -struct Distrs{ - zero_one: Uniform, - one_one: Uniform, -} - -struct RenderProperties { - samples: u32, // samples are averaged results over a pixel - bounces: u32, // bounces are how far the ray will travel (in hits not total distance) +pub struct RenderProperties { + pub samples: u32, // samples are averaged results over a pixel + pub bounces: u32, // bounces are how far the ray will travel (in hits not total distance) } fn to_uv(coord: Vec2i, img_size: Vec2i) -> Vec2f { @@ -35,7 +29,6 @@ fn to_uv(coord: Vec2i, img_size: Vec2i) -> Vec2f { fn ray_color( r: Ray, surface: &Hittable, depth: u32, rng: &mut SmallRng, - distr: &Distrs, ) -> Vec3 { // recursion guard if depth == 0 { @@ -57,7 +50,7 @@ fn ray_color( rng ) { return attenuation * ray_color( - scattered, surface, depth-1, rng, distr + scattered, surface, depth-1, rng ); } } // TODO: explicit else block @@ -79,7 +72,6 @@ fn sample_pixel( img_size: Vec2i, // Supplied by the execution environment (the thread) rng: &mut SmallRng, - dist: &Distrs, ) -> Vec3{ (0..render_props.samples) .fold( @@ -87,22 +79,26 @@ fn sample_pixel( |color, _sample| -> Vec3 { let uv = to_uv(coord, img_size); let ray = scene.camera.get_ray(uv.x, uv.y, rng); - color + ray_color(ray, &scene.world, render_props.bounces, rng, dist) + if ray.dir.x.is_nan() { + panic!("Ray dir.x is NAN"); + } + color + ray_color(ray, &scene.world, render_props.bounces, rng) } ) } -struct Tile { - bounds: Rect, - pixels: Vec, +pub struct Tile { + _bounds: Rect, + pub pixels: Vec, } impl Tile { pub fn render_line( bounds: Rect, y: i32, // bounding rect and line + img_size: Vec2i, scene: &Scene, properties: &RenderProperties, - rng: &mut SmallRng, distr: &Distrs // rng utils + rng: &mut SmallRng, // rng utils ) -> Self { let pixels = (0..bounds.w) .map ( |x| -> Vec3{ @@ -110,12 +106,11 @@ impl Tile { Vec2i{x, y}, scene, properties, - bounds.size(), + img_size, rng, - distr ) }) .collect(); - Self { bounds, pixels } + Self { _bounds: bounds, pixels } } }