Skip to content

Commit 4136ba8

Browse files
committed
factor out rendering code
1 parent 2142bf6 commit 4136ba8

3 files changed

Lines changed: 42 additions & 146 deletions

File tree

src/main.rs

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
use std::sync::Arc;
2-
use std::sync::mpsc::channel;
32

43
use anyhow::Context;
54
use image::RgbImage;
6-
use rand::Rng;
7-
use threadpool::ThreadPool;
85

96
use crate::textures::noise::NoiseType::{Marble, Perlin, Turbulence};
107
use crate::util::worlds::World;
11-
use data::color64::Color64;
128
use image::DynamicImage::ImageRgb8;
139
use std::env;
1410
use util::args::parse_args;
11+
use util::render::render_frame;
1512

1613
mod camera;
1714
mod data;
@@ -54,64 +51,28 @@ fn main() -> anyhow::Result<()> {
5451
return Ok(());
5552
}
5653

57-
let pool = ThreadPool::new(num_cpus::get());
58-
let (tx, rx) = channel::<(u32, Vec<Color64>)>();
59-
60-
(0..world.image_height).for_each(|y| {
61-
let tx = tx.clone();
62-
let world = world.clone();
63-
64-
pool.execute(move || {
65-
let flipped_y = world.image_height - y - 1;
66-
let w = world.image_width as usize;
67-
let mut row = Vec::with_capacity(w);
68-
69-
for x in 0..world.image_width {
70-
let mut pixel_color = Color64::new(0., 0., 0.);
71-
let mut rng = rand::rng();
72-
73-
for _ in 0..world.samples_per_pixel {
74-
let rands: [f64; 2] = rng.random();
75-
76-
let u = (x as f64 + rands[0]) / (world.image_width - 1) as f64;
77-
let v = (y as f64 + rands[1]) / (world.image_height - 1) as f64;
78-
let ray = world.camera.get_ray(u, v);
79-
80-
pixel_color += ray.color_in_world(
81-
&world.hittable,
82-
&world.background_color,
83-
50,
84-
&mut rng,
85-
);
86-
}
87-
88-
row.push(pixel_color);
89-
}
90-
91-
tx.send((flipped_y, row)).expect("no receiver");
92-
});
93-
});
94-
95-
drop(tx);
54+
let rows = render_frame(
55+
world.camera.clone(),
56+
world.clone(),
57+
world.image_width,
58+
world.image_height,
59+
50,
60+
1,
61+
world.samples_per_pixel,
62+
None,
63+
)
64+
.expect("standalone render should not be cancelled");
9665

9766
let mut image = RgbImage::new(world.image_width, world.image_height);
98-
let mut rows_done = 0u32;
99-
100-
for (flipped_y, row) in rx.iter() {
67+
for (flipped_y, row) in &rows {
10168
for (x, pixel_color) in row.iter().enumerate() {
10269
image.put_pixel(
10370
x as u32,
104-
flipped_y,
71+
*flipped_y,
10572
pixel_color.to_image_rgbu8(world.samples_per_pixel),
10673
);
10774
}
108-
109-
rows_done += 1;
110-
println!("{} / {} scanlines done", rows_done, world.image_height);
111-
112-
if rows_done * world.image_width == world.total_pixels() {
113-
break;
114-
}
75+
println!("{} / {} scanlines done", image.height() - flipped_y, world.image_height);
11576
}
11677

11778
ImageRgb8(image).save("output.png")?;

src/util.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod args;
22
pub mod interactive;
33
pub mod obj;
44
mod ppm;
5+
pub mod render;
56
pub mod worlds;
67

78
pub const EPSILON: f64 = 1e-8;

src/util/interactive.rs

Lines changed: 26 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2-
use std::sync::mpsc::channel;
32
use std::sync::{Arc, Mutex};
43
use std::time::{Duration, Instant};
54

65
use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions};
76
use nalgebra::{Rotation3, Unit, Vector3};
8-
use rand::Rng;
9-
use threadpool::ThreadPool;
107

118
use crate::camera::Camera;
129
use crate::data::color64::Color64;
1310
use crate::data::point64::Point64;
11+
use crate::util::render::render_frame;
1412
use crate::util::worlds::World;
1513

1614
const PITCH_LIMIT: f64 = 1.553;
@@ -110,68 +108,6 @@ const RENDER_SCALE: u32 = 2;
110108
/// Max ray bounce depth for interactive rendering. Lower = faster per ray.
111109
const INTERACTIVE_MAX_DEPTH: i32 = 8;
112110

113-
fn render_pass(
114-
pool: &ThreadPool,
115-
world: &Arc<World>,
116-
camera: &Camera,
117-
accum: &mut [Color64],
118-
render_w: u32,
119-
render_h: u32,
120-
view_gen: u64,
121-
generation: &Arc<AtomicU64>,
122-
) -> bool {
123-
let (tx, rx) = channel::<(u32, Vec<Color64>)>();
124-
let rw = render_w as usize;
125-
let du = render_w.saturating_sub(1).max(1) as f64;
126-
let dv = render_h.saturating_sub(1).max(1) as f64;
127-
128-
let mut y = 0u32;
129-
while y < render_h {
130-
let y_end = (y + ROWS_PER_TASK).min(render_h);
131-
let tx = tx.clone();
132-
let world = world.clone();
133-
let camera = camera.clone();
134-
let generation = Arc::clone(generation);
135-
pool.execute(move || {
136-
if generation.load(Ordering::Acquire) != view_gen {
137-
return;
138-
}
139-
let mut rng = rand::rng();
140-
for row_y in y..y_end {
141-
let flipped_y = render_h - row_y - 1;
142-
let mut row = Vec::with_capacity(rw);
143-
for x in 0..render_w {
144-
let u = (x as f64 + rng.random::<f64>()) / du;
145-
let v = (row_y as f64 + rng.random::<f64>()) / dv;
146-
let ray = camera.get_ray(u, v);
147-
let c = ray.color_in_world(
148-
&world.hittable,
149-
&world.background_color,
150-
INTERACTIVE_MAX_DEPTH,
151-
&mut rng,
152-
);
153-
row.push(c);
154-
}
155-
let _ = tx.send((flipped_y, row));
156-
}
157-
});
158-
y = y_end;
159-
}
160-
161-
drop(tx);
162-
163-
for (flipped_y, row) in rx.iter() {
164-
if generation.load(Ordering::Acquire) != view_gen {
165-
return false;
166-
}
167-
let base = flipped_y as usize * rw;
168-
for (x, c) in row.into_iter().enumerate() {
169-
accum[base + x] += c;
170-
}
171-
}
172-
173-
generation.load(Ordering::Acquire) == view_gen
174-
}
175111

176112
/// Tonemap the render-resolution accum buffer into the full-resolution display
177113
/// buffer, upscaling each render pixel to a RENDER_SCALE×RENDER_SCALE block.
@@ -200,11 +136,10 @@ fn tonemap_to_display(
200136
}
201137

202138
fn render_thread(world: Arc<World>, shared: Arc<SharedRender>) {
203-
let pool = ThreadPool::new(num_cpus::get());
204139
let display_w = world.image_width as usize;
205-
let render_w = (world.image_width / RENDER_SCALE) as usize;
206-
let render_h = (world.image_height / RENDER_SCALE) as usize;
207-
let render_len = render_w * render_h;
140+
let render_w = world.image_width / RENDER_SCALE;
141+
let render_h = world.image_height / RENDER_SCALE;
142+
let render_len = (render_w * render_h) as usize;
208143
// Local accumulation buffer at render resolution — no mutex needed.
209144
let mut accum = vec![Color64::new(0., 0., 0.); render_len];
210145

@@ -228,30 +163,29 @@ fn render_thread(world: Arc<World>, shared: Arc<SharedRender>) {
228163

229164
let orbit = shared.orbit.lock().unwrap().clone();
230165
let camera = orbit.to_camera(world.as_ref());
231-
232-
if !render_pass(
233-
&pool,
234-
&world,
235-
&camera,
236-
&mut accum,
237-
render_w as u32,
238-
render_h as u32,
239-
view_gen,
240-
&shared.generation,
241-
) {
242-
break;
166+
let cancel = Some((shared.generation.clone(), view_gen));
167+
168+
match render_frame(camera, world.clone(), render_w, render_h, INTERACTIVE_MAX_DEPTH, ROWS_PER_TASK, 1, cancel) {
169+
None => break,
170+
Some(rows) => {
171+
for (flipped_y, row) in rows {
172+
let base = flipped_y as usize * render_w as usize;
173+
for (x, c) in row.into_iter().enumerate() {
174+
accum[base + x] += c;
175+
}
176+
}
177+
local_samples += 1;
178+
shared.samples.store(local_samples, Ordering::Release);
179+
tonemap_to_display(
180+
&accum,
181+
local_samples,
182+
&mut shared.display.lock().unwrap(),
183+
render_w as usize,
184+
render_h as usize,
185+
display_w,
186+
);
187+
}
243188
}
244-
local_samples += 1;
245-
shared.samples.store(local_samples, Ordering::Release);
246-
247-
tonemap_to_display(
248-
&accum,
249-
local_samples,
250-
&mut shared.display.lock().unwrap(),
251-
render_w,
252-
render_h,
253-
display_w,
254-
);
255189
}
256190
}
257191
}

0 commit comments

Comments
 (0)