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:
115
src/main.rs
115
src/main.rs
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user