From 31b218c69834cd88a2c72b0eae3f4059fde3c956 Mon Sep 17 00:00:00 2001 From: Walther Date: Tue, 31 Oct 2023 12:35:02 +0200 Subject: [PATCH 1/7] chore: cargo upgrade --- clovers-cli/Cargo.toml | 12 ++++++------ clovers/Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clovers-cli/Cargo.toml b/clovers-cli/Cargo.toml index c8240161..04546764 100644 --- a/clovers-cli/Cargo.toml +++ b/clovers-cli/Cargo.toml @@ -18,12 +18,12 @@ 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"] } @@ -31,9 +31,9 @@ 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"] } diff --git a/clovers/Cargo.toml b/clovers/Cargo.toml index 86ae4580..edfe8f59 100644 --- a/clovers/Cargo.toml +++ b/clovers/Cargo.toml @@ -24,11 +24,11 @@ nalgebra = { version = "0.32.3", features = ["libm"], default-features = false } 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" From 2a3d25340f2e79779f1ec1ea008587624fd7ac26 Mon Sep 17 00:00:00 2001 From: Walther Date: Tue, 31 Oct 2023 14:09:48 +0200 Subject: [PATCH 2/7] chore: add profiling helpers --- .gitignore | 3 +++ Cargo.toml | 4 ++++ Justfile | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/.gitignore b/.gitignore index 6a58583c..5ebd9b58 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ Cargo.lock # some scene files are not checked into version control local_scenes + +# profiling +profile.json diff --git a/Cargo.toml b/Cargo.toml index 578be356..e6a98435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,7 @@ members = ["clovers", "clovers-cli"] [profile.release] codegen-units = 1 lto = "fat" + +[profile.profiling] +inherits = "release" +debug = true diff --git a/Justfile b/Justfile index 0fa31ec7..43b44fc2 100644 --- a/Justfile +++ b/Justfile @@ -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}} From eff1fb9b93a8f2f0edd28083fbd576acd25f1a7f Mon Sep 17 00:00:00 2001 From: Walther Date: Tue, 31 Oct 2023 16:37:02 +0200 Subject: [PATCH 3/7] refactor: draw_cpu.rs improvements --- clovers-cli/src/draw_cpu.rs | 132 ++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 51 deletions(-) diff --git a/clovers-cli/src/draw_cpu.rs b/clovers-cli/src/draw_cpu.rs index 389ab6d1..666fbd73 100644 --- a/clovers-cli/src/draw_cpu.rs +++ b/clovers-cli/src/draw_cpu.rs @@ -13,68 +13,77 @@ use std::time::Duration; /// The main drawing function, returns a `Vec` as a pixelbuffer. pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec> { - // Progress bar let pixels = (opts.width * opts.height) as u64; - let bar = ProgressBar::new(pixels); - - 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 bar = progress_bar(&opts, pixels); + // Initialize a pixelbuffer let black: Srgb = Srgb::new(0, 0, 0); let mut pixelbuffer = vec![black; pixels as usize]; + // Rendering time! 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 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 - } + *pixel = render_pixel_normalmap(scene, &opts, index, &bar); + } else { + *pixel = render_pixel(scene, &opts, index, &bar); } - color /= opts.samples as Float; - // Gamma / component transfer function - let color: Srgb = color.into_color(); - *pixel = color.into_format(); - - bar.inc(1); }); pixelbuffer } +// Render a single pixel, including possible multisampling +fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize, bar: &ProgressBar) -> Srgb { + let (x, y, width, height) = index_to_params(opts, index); + let mut rng = SmallRng::from_entropy(); + 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, &mut rng, opts.max_depth) { + color += s + } + } + color /= opts.samples as Float; + let color: Srgb = color.into_color(); + let color: Srgb = color.into_format(); + bar.inc(1); + color +} + +// Render a single pixel in normalmap mode +fn render_pixel_normalmap( + scene: &Scene, + opts: &RenderOpts, + index: usize, + bar: &ProgressBar, +) -> Srgb { + let (x, y, width, height) = index_to_params(opts, index); + let mut rng = SmallRng::from_entropy(); + let color: LinSrgb = sample_normalmap(scene, x, y, width, height, &mut rng); + let color: Srgb = color.into_color(); + let color: Srgb = color.into_format(); + bar.inc(1); + 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, @@ -88,11 +97,32 @@ fn sample( let u = (x + rng.gen::()) / width; let v = (y + rng.gen::()) / height; let ray: Ray = scene.camera.get_ray(u, v, rng); - let new_color: Xyz = 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 = 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, pixels: u64) -> ProgressBar { + let bar = ProgressBar::new(pixels); + if opts.quiet { + bar.set_draw_target(ProgressDrawTarget::hidden()) + } else { + bar.set_style(ProgressStyle::default_bar().template( + "Elapsed: {elapsed_precise}\nPixels: {bar} {pos}/{len}\nRemaining: {eta_precise}", + ).unwrap()); + bar.enable_steady_tick(Duration::from_millis(100)); + } + bar +} From d9d970939bd1360d3e8616591085df248136571c Mon Sep 17 00:00:00 2001 From: Walther Date: Wed, 1 Nov 2023 18:40:54 +0200 Subject: [PATCH 4/7] chore: remove libm from nalgebra, allow default features --- clovers/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clovers/Cargo.toml b/clovers/Cargo.toml index edfe8f59..08caecbe 100644 --- a/clovers/Cargo.toml +++ b/clovers/Cargo.toml @@ -20,7 +20,7 @@ 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" From 08163aa2f64db133dafb99e97ce17a579f991bc4 Mon Sep 17 00:00:00 2001 From: Walther Date: Wed, 1 Nov 2023 18:54:44 +0200 Subject: [PATCH 5/7] refactor: minor improvement to draw_cpu --- clovers-cli/src/draw_cpu.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/clovers-cli/src/draw_cpu.rs b/clovers-cli/src/draw_cpu.rs index 666fbd73..75096c0d 100644 --- a/clovers-cli/src/draw_cpu.rs +++ b/clovers-cli/src/draw_cpu.rs @@ -26,17 +26,18 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec> { .enumerate() .for_each(|(index, pixel)| { if opts.normalmap { - *pixel = render_pixel_normalmap(scene, &opts, index, &bar); + *pixel = render_pixel_normalmap(scene, &opts, index); } else { - *pixel = render_pixel(scene, &opts, index, &bar); + *pixel = render_pixel(scene, &opts, index); } + bar.inc(1); }); pixelbuffer } // Render a single pixel, including possible multisampling -fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize, bar: &ProgressBar) -> Srgb { +fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize) -> Srgb { let (x, y, width, height) = index_to_params(opts, index); let mut rng = SmallRng::from_entropy(); let mut color: LinSrgb = LinSrgb::new(0.0, 0.0, 0.0); @@ -48,23 +49,16 @@ fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize, bar: &ProgressBa color /= opts.samples as Float; let color: Srgb = color.into_color(); let color: Srgb = color.into_format(); - bar.inc(1); color } // Render a single pixel in normalmap mode -fn render_pixel_normalmap( - scene: &Scene, - opts: &RenderOpts, - index: usize, - bar: &ProgressBar, -) -> Srgb { +fn render_pixel_normalmap(scene: &Scene, opts: &RenderOpts, index: usize) -> Srgb { let (x, y, width, height) = index_to_params(opts, index); let mut rng = SmallRng::from_entropy(); let color: LinSrgb = sample_normalmap(scene, x, y, width, height, &mut rng); let color: Srgb = color.into_color(); let color: Srgb = color.into_format(); - bar.inc(1); color } From 152b6494c67eb53e53864d4a4b50f18babe068b4 Mon Sep 17 00:00:00 2001 From: Walther Date: Wed, 1 Nov 2023 19:00:56 +0200 Subject: [PATCH 6/7] refactor: minor improvement in draw_cpu --- clovers-cli/src/draw_cpu.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/clovers-cli/src/draw_cpu.rs b/clovers-cli/src/draw_cpu.rs index 75096c0d..0b7edcaf 100644 --- a/clovers-cli/src/draw_cpu.rs +++ b/clovers-cli/src/draw_cpu.rs @@ -25,10 +25,11 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec> { .par_iter_mut() .enumerate() .for_each(|(index, pixel)| { + let mut rng = SmallRng::from_entropy(); if opts.normalmap { - *pixel = render_pixel_normalmap(scene, &opts, index); + *pixel = render_pixel_normalmap(scene, &opts, index, &mut rng); } else { - *pixel = render_pixel(scene, &opts, index); + *pixel = render_pixel(scene, &opts, index, &mut rng); } bar.inc(1); }); @@ -37,12 +38,11 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec> { } // Render a single pixel, including possible multisampling -fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize) -> Srgb { +fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize, rng: &mut SmallRng) -> Srgb { let (x, y, width, height) = index_to_params(opts, index); - let mut rng = SmallRng::from_entropy(); 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, &mut rng, opts.max_depth) { + if let Some(s) = sample(scene, x, y, width, height, rng, opts.max_depth) { color += s } } @@ -53,10 +53,14 @@ fn render_pixel(scene: &Scene, opts: &RenderOpts, index: usize) -> Srgb { } // Render a single pixel in normalmap mode -fn render_pixel_normalmap(scene: &Scene, opts: &RenderOpts, index: usize) -> Srgb { +fn render_pixel_normalmap( + scene: &Scene, + opts: &RenderOpts, + index: usize, + rng: &mut SmallRng, +) -> Srgb { let (x, y, width, height) = index_to_params(opts, index); - let mut rng = SmallRng::from_entropy(); - let color: LinSrgb = sample_normalmap(scene, x, y, width, height, &mut rng); + let color: LinSrgb = sample_normalmap(scene, x, y, width, height, rng); let color: Srgb = color.into_color(); let color: Srgb = color.into_format(); color From 0108cb2e760b0bfc40039e76e931bb2c4b8ef757 Mon Sep 17 00:00:00 2001 From: Walther Date: Wed, 1 Nov 2023 22:21:30 +0200 Subject: [PATCH 7/7] refactor: improve main draw function, 5% perf gain --- clovers-cli/src/draw_cpu.rs | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/clovers-cli/src/draw_cpu.rs b/clovers-cli/src/draw_cpu.rs index 0b7edcaf..7b5af8cb 100644 --- a/clovers-cli/src/draw_cpu.rs +++ b/clovers-cli/src/draw_cpu.rs @@ -9,30 +9,31 @@ 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` as a pixelbuffer. pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec> { - let pixels = (opts.width * opts.height) as u64; - let bar = progress_bar(&opts, pixels); + let width = opts.width as usize; + let height = opts.height as usize; + let bar = progress_bar(&opts); - // Initialize a pixelbuffer - let black: Srgb = Srgb::new(0, 0, 0); - let mut pixelbuffer = vec![black; pixels as usize]; - - // Rendering time! - pixelbuffer - .par_iter_mut() - .enumerate() - .for_each(|(index, pixel)| { + let pixelbuffer: Vec> = (0..height) + .into_par_iter() + .map(|row_index| { let mut rng = SmallRng::from_entropy(); - if opts.normalmap { - *pixel = render_pixel_normalmap(scene, &opts, index, &mut rng); - } else { - *pixel = render_pixel(scene, &opts, index, &mut rng); + 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)); + } } bar.inc(1); - }); + row + }) + .flatten() + .collect(); pixelbuffer } @@ -112,15 +113,14 @@ fn index_to_params(opts: &RenderOpts, index: usize) -> (Float, Float, Float, Flo (x, y, width, height) } -fn progress_bar(opts: &RenderOpts, pixels: u64) -> ProgressBar { - let bar = ProgressBar::new(pixels); +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}\nPixels: {bar} {pos}/{len}\nRemaining: {eta_precise}", + "Elapsed: {elapsed_precise}\nRows: {bar} {pos}/{len}\nRemaining: {eta_precise}", ).unwrap()); - bar.enable_steady_tick(Duration::from_millis(100)); } bar }