Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2023-10-31 work #192

Merged
merged 7 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ Cargo.lock

# some scene files are not checked into version control
local_scenes

# profiling
profile.json
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ members = ["clovers", "clovers-cli"]
[profile.release]
codegen-units = 1
lto = "fat"

[profile.profiling]
inherits = "release"
debug = true
5 changes: 5 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ all-scenes:
for scene in $(ls scenes/ |grep json); \
do just cli -s 1 --input scenes/$scene --output renders/$DATE/${scene%.json}.png; \
done;

# Profiling helper
profile *ARGS:
cargo build --profile profiling; \
samply record -- ./target/profiling/clovers-cli {{ARGS}}
12 changes: 6 additions & 6 deletions clovers-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ clovers = { path = "../clovers", features = [
], default-features = false }

# External
clap = { version = "4.4.1", features = ["std", "derive"] }
clap = { version = "4.4.7", features = ["std", "derive"] }
human_format = "1.0.3"
humantime = "2.1.0"
image = { version = "0.24.7", features = ["png"], default-features = false }
img-parts = "0.3.0"
indicatif = { version = "0.17.6", features = [
indicatif = { version = "0.17.7", features = [
"rayon",
], default-features = false }
palette = { version = "0.7.3", features = ["serializing"] }
rand = { version = "0.8.5", features = [
"small_rng",
"getrandom",
], default-features = false }
rayon = "1.7.0"
serde = { version = "1.0.188", features = ["derive"], default-features = false }
rayon = "1.8.0"
serde = { version = "1.0.190", features = ["derive"], default-features = false }
serde_json = { version = "1.0", features = ["alloc"], default-features = false }
time = { version = "0.3.28", default-features = false }
tracing = "0.1.37"
time = { version = "0.3.30", default-features = false }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.17", features = ["time"] }
146 changes: 87 additions & 59 deletions clovers-cli/src/draw_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,80 @@ use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use rayon::prelude::*;
use scenes::Scene;
use std::time::Duration;

/// The main drawing function, returns a `Vec<Srgb>` as a pixelbuffer.
pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Srgb<u8>> {
// Progress bar
let pixels = (opts.width * opts.height) as u64;
let bar = ProgressBar::new(pixels);
let width = opts.width as usize;
let height = opts.height as usize;
let bar = progress_bar(&opts);

if opts.quiet {
bar.set_draw_target(ProgressDrawTarget::hidden())
} else {
bar.set_style(ProgressStyle::default_bar().template(
"Elapsed: {elapsed_precise}\nPixels: {bar} {pos}/{len}\nETA: {eta_precise}",
).unwrap());
bar.enable_steady_tick(Duration::from_millis(100));
}

let black: Srgb<u8> = Srgb::new(0, 0, 0);
let mut pixelbuffer = vec![black; pixels as usize];

pixelbuffer
.par_iter_mut()
.enumerate()
.for_each(|(index, pixel)| {
// Enumerate gives us an usize, opts.width and opts.height are u32
// Most internal functions expect a Float, perform conversions
let x = (index % (opts.width as usize)) as Float;
let y = (index / (opts.width as usize)) as Float;
let width = opts.width as Float;
let height = opts.height as Float;

// Initialize a thread-local random number generator
let pixelbuffer: Vec<Srgb<u8>> = (0..height)
.into_par_iter()
.map(|row_index| {
let mut rng = SmallRng::from_entropy();

// Initialize a mutable base color for the pixel
let mut color: LinSrgb = LinSrgb::new(0.0, 0.0, 0.0);

if opts.normalmap {
// If we are rendering just a normalmap, make it quick and early return
let u = x / width;
let v = y / height;
let ray: Ray = scene.camera.get_ray(u, v, &mut rng);
color = normal_map(&ray, scene, &mut rng);
let color: Srgb = color.into_color();
*pixel = color.into_format();
return;
}
// Otherwise, do a regular render

// Multisampling for antialiasing
for _sample in 0..opts.samples {
if let Some(s) = sample(scene, x, y, width, height, &mut rng, opts.max_depth) {
color += s
let mut row = Vec::with_capacity(width);
for index in 0..width {
let index = index + row_index * width;
if opts.normalmap {
row.push(render_pixel_normalmap(scene, &opts, index, &mut rng));
} else {
row.push(render_pixel(scene, &opts, index, &mut rng));
}
}
color /= opts.samples as Float;
// Gamma / component transfer function
let color: Srgb = color.into_color();
*pixel = color.into_format();

bar.inc(1);
});
row
})
.flatten()
.collect();

pixelbuffer
}

// Render a single pixel, including possible multisampling
fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize, rng: &mut SmallRng) -> Srgb<u8> {
let (x, y, width, height) = index_to_params(opts, index);
let mut color: LinSrgb = LinSrgb::new(0.0, 0.0, 0.0);
for _sample in 0..opts.samples {
if let Some(s) = sample(scene, x, y, width, height, rng, opts.max_depth) {
color += s
}
}
color /= opts.samples as Float;
let color: Srgb = color.into_color();
let color: Srgb<u8> = color.into_format();
color
}

// Render a single pixel in normalmap mode
fn render_pixel_normalmap(
scene: &Scene,
opts: &RenderOpts,
index: usize,
rng: &mut SmallRng,
) -> Srgb<u8> {
let (x, y, width, height) = index_to_params(opts, index);
let color: LinSrgb = sample_normalmap(scene, x, y, width, height, rng);
let color: Srgb = color.into_color();
let color: Srgb<u8> = color.into_format();
color
}

// Get a single sample for a single pixel in the scene, normalmap mode.
fn sample_normalmap(
scene: &Scene,
x: Float,
y: Float,
width: Float,
height: Float,
rng: &mut SmallRng,
) -> LinSrgb {
let u = x / width;
let v = y / height;
let ray: Ray = scene.camera.get_ray(u, v, rng);
let color = normal_map(&ray, scene, rng);
color.into_color()
}

/// Get a single sample for a single pixel in the scene. Has slight jitter for antialiasing when multisampling.
fn sample(
scene: &Scene,
Expand All @@ -88,11 +96,31 @@ fn sample(
let u = (x + rng.gen::<Float>()) / width;
let v = (y + rng.gen::<Float>()) / height;
let ray: Ray = scene.camera.get_ray(u, v, rng);
let new_color: Xyz<E> = colorize(&ray, scene, 0, max_depth, rng);
let new_color: Xyz = new_color.adapt_into();
let new_color: LinSrgb = new_color.into_color_unclamped();
if new_color.red.is_finite() && new_color.green.is_finite() && new_color.blue.is_finite() {
return Some(new_color);
let color: Xyz<E> = colorize(&ray, scene, 0, max_depth, rng);
let color: Xyz = color.adapt_into();
let color: LinSrgb = color.into_color_unclamped();
if color.red.is_finite() && color.green.is_finite() && color.blue.is_finite() {
return Some(color);
}
None
}

fn index_to_params(opts: &RenderOpts, index: usize) -> (Float, Float, Float, Float) {
let x = (index % (opts.width as usize)) as Float;
let y = (index / (opts.width as usize)) as Float;
let width = opts.width as Float;
let height = opts.height as Float;
(x, y, width, height)
}

fn progress_bar(opts: &RenderOpts) -> ProgressBar {
let bar = ProgressBar::new(opts.height as u64);
if opts.quiet {
bar.set_draw_target(ProgressDrawTarget::hidden())
} else {
bar.set_style(ProgressStyle::default_bar().template(
"Elapsed: {elapsed_precise}\nRows: {bar} {pos}/{len}\nRemaining: {eta_precise}",
).unwrap());
}
bar
}
6 changes: 3 additions & 3 deletions clovers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ traces = ["tracing"]
[dependencies]
enum_dispatch = "0.3.12"
gltf = { version = "1.3.0", optional = true }
nalgebra = { version = "0.32.3", features = ["libm"], default-features = false }
nalgebra = { version = "0.32.3" }
palette = { version = "0.7.3", features = ["serializing"] }
rand = { version = "0.8.5", features = ["small_rng"], default-features = false }
rand_distr = "0.4.3"
serde = { version = "1.0.188", features = [
serde = { version = "1.0.190", features = [
"derive",
], default-features = false, optional = true }
stl_io = { version = "0.7.0", optional = true }
tracing = { version = "0.1.37", optional = true }
tracing = { version = "0.1.40", optional = true }

[dev-dependencies]
criterion = "0.4.0"
Expand Down