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.
This commit is contained in:
2023-09-25 10:50:47 -07:00
parent f03c6280a7
commit 3250f8e580
2 changed files with 48 additions and 106 deletions

View File

@@ -3,26 +3,37 @@ mod primitives;
mod scene; mod scene;
mod renderer; mod renderer;
use crate::primitives::Vec3; use crate::primitives::{
Vec2i,
Vec3,
Rect,
};
use crate::scene::{ use crate::scene::{
Camera, Camera,
Scene Scene
}; };
use crate::renderer::RenderCommand;
use crate::renderer::{
Tile,
RenderProperties,
};
use rand::SeedableRng; use rand::SeedableRng;
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use std::thread;
fn main() { fn main() {
// image // image
let aspect_ratio = 3.0 / 2.0; let aspect_ratio = 3.0 / 2.0;
let image = ( let image = Vec2i {
400, x: 400,
(400.0 / aspect_ratio) as i32 y: (400.0 / aspect_ratio) as i32
); };
let samples_per_pixel: u32 = 10;
let max_depth = 50; let render_config = RenderProperties {
samples: 10,
bounces: 50
};
// random generator // random generator
let mut small_rng = SmallRng::seed_from_u64(0); let mut small_rng = SmallRng::seed_from_u64(0);
@@ -44,84 +55,20 @@ fn main() {
// render // render
// The render loop should now be a job submission mechanism // The render loop should now be a job submission mechanism
// Iterate lines, submitting them as tasks to the thread. // Iterate lines, submitting them as tasks to the thread.
println!("P3\n{} {}\n255", image.0, image.1); println!("P3\n{} {}\n255", image.x, image.y);
let context = renderer::RenderContext { for row in (0..image.y).rev() {
camera: &scene.camera, let bounds = Rect{ // render boundary (a horizontal slice)
image, x: 0,
max_depth, y: row,
samples_per_pixel, w: image.x,
world: scene.world, h: 1
}; };
let tile = Tile::render_line(bounds, row, image, &scene, &render_config, &mut small_rng);
thread::scope(|s| { eprintln!("Printing scanline #{}", row);
let (mut dispatcher, scanline_receiver) = renderer::Dispatcher::new(&small_rng, 12); for pixel in tile.pixels {
println!("{}", pixel.print_ppm(render_config.samples))
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::<renderer::RenderResult>::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;
} }
} }
}
eprintln!("Size of raster_segments at finish: {}", raster_segments.len());
});
// TODO: Dispatcher shutdown mechanism. Right now, we might technically be leaking threads. // TODO: Dispatcher shutdown mechanism. Right now, we might technically be leaking threads.
eprintln!("Done!"); 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));
}
}

View File

@@ -12,18 +12,12 @@ use crate::scene::{
}; };
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use rand::distributions::Uniform;
const SKY_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.7, z: 1.0}; const SKY_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.7, z: 1.0};
struct Distrs{ pub struct RenderProperties {
zero_one: Uniform<f32>, pub samples: u32, // samples are averaged results over a pixel
one_one: Uniform<f32>, pub bounces: u32, // bounces are how far the ray will travel (in hits not total distance)
}
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)
} }
fn to_uv(coord: Vec2i, img_size: Vec2i) -> Vec2f { 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( fn ray_color(
r: Ray, surface: &Hittable, depth: u32, r: Ray, surface: &Hittable, depth: u32,
rng: &mut SmallRng, rng: &mut SmallRng,
distr: &Distrs,
) -> Vec3 { ) -> Vec3 {
// recursion guard // recursion guard
if depth == 0 { if depth == 0 {
@@ -57,7 +50,7 @@ fn ray_color(
rng rng
) { ) {
return attenuation * ray_color( return attenuation * ray_color(
scattered, surface, depth-1, rng, distr scattered, surface, depth-1, rng
); );
} }
} // TODO: explicit else block } // TODO: explicit else block
@@ -79,7 +72,6 @@ fn sample_pixel(
img_size: Vec2i, img_size: Vec2i,
// Supplied by the execution environment (the thread) // Supplied by the execution environment (the thread)
rng: &mut SmallRng, rng: &mut SmallRng,
dist: &Distrs,
) -> Vec3{ ) -> Vec3{
(0..render_props.samples) (0..render_props.samples)
.fold( .fold(
@@ -87,22 +79,26 @@ fn sample_pixel(
|color, _sample| -> Vec3 { |color, _sample| -> Vec3 {
let uv = to_uv(coord, img_size); let uv = to_uv(coord, img_size);
let ray = scene.camera.get_ray(uv.x, uv.y, rng); 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 { pub struct Tile {
bounds: Rect, _bounds: Rect,
pixels: Vec<Vec3>, pub pixels: Vec<Vec3>,
} }
impl Tile { impl Tile {
pub fn render_line( pub fn render_line(
bounds: Rect, y: i32, // bounding rect and line bounds: Rect, y: i32, // bounding rect and line
img_size: Vec2i,
scene: &Scene, scene: &Scene,
properties: &RenderProperties, properties: &RenderProperties,
rng: &mut SmallRng, distr: &Distrs // rng utils rng: &mut SmallRng, // rng utils
) -> Self { ) -> Self {
let pixels = (0..bounds.w) let pixels = (0..bounds.w)
.map ( |x| -> Vec3{ .map ( |x| -> Vec3{
@@ -110,12 +106,11 @@ impl Tile {
Vec2i{x, y}, Vec2i{x, y},
scene, scene,
properties, properties,
bounds.size(), img_size,
rng, rng,
distr
) )
}) })
.collect(); .collect();
Self { bounds, pixels } Self { _bounds: bounds, pixels }
} }
} }