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 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::<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;
}
}
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));
}
}

View File

@@ -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<f32>,
one_one: Uniform<f32>,
}
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<Vec3>,
pub struct Tile {
_bounds: Rect,
pub pixels: Vec<Vec3>,
}
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 }
}
}