pbrt_r3/integrators/sppm.rs
1use crate::core::prelude::*;
2use crate::samplers::*;
3
4use std::ops::DerefMut;
5use std::sync::Arc;
6use std::sync::Mutex;
7use std::sync::RwLock;
8
9use rayon::iter::IntoParallelIterator;
10use rayon::iter::IntoParallelRefIterator;
11use rayon::iter::ParallelIterator;
12
13thread_local!(static PHOTON_PATHS: StatCounter = StatCounter::new("Stochastic Progressive Photon Mapping/Photon paths followed"));
14thread_local!(static GRID_CELLS_PER_VISIBLE_POINT: StatIntDistribution = StatIntDistribution::new("Stochastic Progressive Photon Mapping/Grid cells per visible point"));
15thread_local!(static PIXEL_MEMORY_BYTES: StatMemoryCounter = StatMemoryCounter::new("Memory/SPPM Pixels"));
16
17#[derive(Clone)]
18struct SPPMVisiblePoint {
19 p: Point3f,
20 wo: Vector3f,
21 bsdf: Option<Arc<BSDF>>,
22 beta: Spectrum,
23}
24
25struct SPPMPixel {
26 // SPPMPixel Public Methods
27 radius: Float,
28 ld: Spectrum,
29 vp: SPPMVisiblePoint, // VisiblePoint vp;
30 phi: Spectrum,
31 m: i64,
32 n: Float,
33 tau: Spectrum,
34}
35
36#[derive(Clone, Default)]
37struct SPPMTile {
38 pixels: Vec<Arc<RwLock<SPPMPixel>>>,
39}
40impl SPPMTile {
41 pub fn push(&mut self, pixel: Arc<RwLock<SPPMPixel>>) {
42 self.pixels.push(pixel);
43 }
44}
45unsafe impl Sync for SPPMTile {}
46unsafe impl Send for SPPMTile {}
47
48#[derive(Clone)]
49struct SPPMPixelListNode {
50 pixel: Arc<RwLock<SPPMPixel>>,
51 next: Option<Arc<SPPMPixelListNode>>,
52}
53
54#[derive(Clone)]
55struct SPPMHashGrid {
56 nodes: Vec<Option<Arc<SPPMPixelListNode>>>,
57}
58unsafe impl Sync for SPPMHashGrid {}
59unsafe impl Send for SPPMHashGrid {}
60
61pub struct SPPMIntegrator {
62 pub camera: Arc<dyn Camera>,
63 pub initial_search_radius: Float,
64 pub n_iterations: u32,
65 pub max_depth: i32,
66 pub photons_per_iteration: i32,
67 pub write_frequency: i32,
68}
69
70impl SPPMIntegrator {
71 pub fn new(
72 camera: Arc<dyn Camera>,
73 initial_search_radius: Float,
74 n_iterations: i32,
75 max_depth: i32,
76 photons_per_iteration: i32,
77 write_frequency: i32,
78 ) -> Self {
79 let photons_per_iteration = if photons_per_iteration > 0 {
80 photons_per_iteration
81 } else {
82 let camera = camera.as_ref();
83 let film = camera.get_film();
84 let film = film.read().unwrap();
85 film.cropped_pixel_bounds.area() as i32
86 };
87 let n_iterations = n_iterations as u32;
88 Self {
89 camera,
90 initial_search_radius,
91 n_iterations,
92 max_depth,
93 photons_per_iteration,
94 write_frequency,
95 }
96 }
97
98 fn get_film(&self) -> Arc<RwLock<Film>> {
99 let camera = self.camera.as_ref();
100 camera.get_film()
101 }
102}
103
104#[inline]
105fn to_grid(p: &Point3f, bounds: &Bounds3f, grid_res: &[i32; 3]) -> (bool, Point3i) {
106 let mut in_bounds = true;
107 let pg = bounds.offset(p);
108 let mut pi = Point3i::default();
109 for i in 0..3 {
110 if pg[i] < 0.0 || pg[i] > 1.0 {
111 in_bounds = false;
112 }
113 pi[i] = (grid_res[i] as Float * pg[i]) as i32;
114 pi[i] = i32::clamp(pi[i], 0, grid_res[i] - 1);
115 }
116 return (in_bounds, pi);
117}
118
119#[inline]
120fn hash(p: &Point3i, hash_size: usize) -> usize {
121 let x = p.x as usize * 73856093;
122 let y = p.y as usize * 19349663;
123 let z = p.z as usize * 83492791;
124 let h = (x ^ y ^ z) % hash_size;
125 return h;
126}
127
128impl Integrator for SPPMIntegrator {
129 fn render(&mut self, scene: &Scene) {
130 let pixel_bounds = {
131 let film = self.get_film();
132 let film = film.read().unwrap();
133 film.cropped_pixel_bounds
134 };
135
136 {
137 let film = self.get_film();
138 let mut film = film.write().unwrap();
139 film.render_start();
140 }
141
142 let n_pixels = pixel_bounds.area() as usize;
143 let initial_search_radius = self.initial_search_radius;
144 let pixels = Arc::new(RwLock::new(SPPMTile::default()));
145
146 PIXEL_MEMORY_BYTES.with(|c| c.add(n_pixels * std::mem::size_of::<SPPMPixel>()));
147
148 {
149 let mut pixels = pixels.write().unwrap();
150 for _ in 0..n_pixels {
151 let pixel = Arc::new(RwLock::new(SPPMPixel {
152 radius: initial_search_radius,
153 ld: Spectrum::default(),
154 vp: SPPMVisiblePoint {
155 p: Point3f::default(),
156 wo: Vector3f::default(),
157 bsdf: None,
158 beta: Spectrum::zero(),
159 },
160 phi: Spectrum::zero(),
161 m: 0,
162 n: 0.0,
163 tau: Spectrum::zero(),
164 }));
165 pixels.push(pixel);
166 }
167 }
168
169 // Compute _lightDistr_ for sampling lights proportional to power
170 let light_distr = compute_light_power_distribution(scene);
171
172 let n_iterations = self.n_iterations;
173 let inv_sqrt_spp = 1.0 / Float::sqrt(n_iterations as Float);
174 // Perform _nIterations_ of SPPM integration
175 let sampler = HaltonSampler::new(n_iterations, &pixel_bounds, false);
176
177 // Compute number of tiles to use for SPPM camera pass
178 let pixel_extent = pixel_bounds.diagonal();
179 let tile_size = 16;
180 let n_tiles = Point2i::new(
181 (pixel_extent.x + tile_size - 1) / tile_size,
182 (pixel_extent.y + tile_size - 1) / tile_size,
183 );
184
185 let mut tiles = Vec::with_capacity(n_tiles.x as usize * n_tiles.y as usize);
186 //let mut samplers = Vec::with_capacity(n_tiles.x as usize * n_tiles.y as usize);
187 for y in 0..n_tiles.y {
188 for x in 0..n_tiles.x {
189 let x0 = pixel_bounds.min.x + x * tile_size;
190 let x1 = std::cmp::min(x0 + tile_size, pixel_bounds.max.x);
191 let y0 = pixel_bounds.min.y + y * tile_size;
192 let y1 = std::cmp::min(y0 + tile_size, pixel_bounds.max.y);
193 let tile_bounds = Bounds2i::new(&Point2i::new(x0, y0), &Point2i::new(x1, y1));
194 let tile_index = y * n_tiles.x + x;
195 let sampler = sampler.clone_with_seed(tile_index as u32);
196 let sampler = Arc::new(Mutex::new(ProxySampler::new(&sampler)));
197 //
198 tiles.push((x, y, tile_bounds, sampler));
199 //samplers.push(sampler);
200 }
201 }
202
203 let progress = Arc::new(RwLock::new(ProgressReporter::new(
204 2 * n_iterations as usize,
205 "Rendering",
206 )));
207
208 let camera = self.get_camera();
209 //let camera = camera.deref();
210 let max_depth = self.max_depth;
211 for iter in 0..n_iterations {
212 // Generate SPPM visible points
213 {
214 tiles.par_iter().for_each(|tile| {
215 let mut arena = MemoryArena::new();
216 //let pixel_bounds = tile.5;
217 let pixels = pixels.clone();
218 let pixels = pixels.read().unwrap();
219 let camera = camera.as_ref();
220 //let tile = Point2i::new(tile.0, tile.1);
221 let tile_bounds = tile.2;
222 //let tile_index = tile.1 * n_tiles.x + tile.0;
223 let tile_sampler = tile.3.clone();
224 let mut tile_sampler = tile_sampler.lock().unwrap();
225 // Follow camera paths for _tile_ in image for SPPM
226 for y in tile_bounds.min.y..tile_bounds.max.y {
227 for x in tile_bounds.min.x..tile_bounds.max.x {
228 let p_pixel = Point2i::new(x, y);
229 tile_sampler.start_pixel(&p_pixel);
230 tile_sampler.set_sample_number(iter);
231
232 // Generate camera ray for pixel for SPPM
233 let camera_sample = tile_sampler.get_camera_sample(&p_pixel);
234 if let Some((beta, mut ray)) =
235 camera.generate_ray_differential(&camera_sample)
236 {
237 if beta <= 0.0 {
238 continue;
239 }
240 ray.scale_differentials(inv_sqrt_spp);
241
242 let mut beta = Spectrum::from(beta);
243
244 // Follow camera ray path until a visible point is created
245
246 // Get _SPPMPixel_ for _pPixel_
247 let p_pixel_o = p_pixel - pixel_bounds.min;
248 let pixel_offset = p_pixel_o.x
249 + p_pixel_o.y * (pixel_bounds.max.x - pixel_bounds.min.x);
250 let pixel = pixels.pixels[pixel_offset as usize].clone();
251 let mut specular_bounce = false;
252 let mut depth = 0;
253 while depth < max_depth {
254 if let Some(mut isect) = scene.intersect(&ray.ray) {
255 // Process SPPM camera ray intersection
256
257 // Compute BSDF at SPPM camera ray intersection
258 {
259 isect.compute_scattering_functions(
260 &ray,
261 &mut arena,
262 TransportMode::Radiance,
263 true,
264 );
265 }
266 //isect.ComputeScatteringFunctions(ray, arena, true);
267 if let Some(bsdf) = isect.bsdf.as_ref() {
268 let tisect = Interaction::from(&isect);
269 // Accumulate direct illumination at SPPM camera ray
270 // intersection
271 let wo = -ray.ray.d;
272 if depth == 0 || specular_bounce {
273 let mut pixel = pixel.write().unwrap();
274 pixel.ld += beta * isect.le(&wo);
275 }
276 {
277 let mut pixel = pixel.write().unwrap();
278 pixel.ld += beta
279 * uniform_sample_one_light(
280 &tisect,
281 scene,
282 &mut arena,
283 tile_sampler.deref_mut()
284 as &mut dyn Sampler,
285 false,
286 None,
287 );
288 }
289 // Possibly create visible point and end camera path
290
291 let (is_diffuse, is_glossy) = {
292 let bsdf = bsdf.as_ref();
293 let b1 = bsdf.num_components(
294 BSDF_DIFFUSE
295 | BSDF_REFLECTION
296 | BSDF_TRANSMISSION,
297 ) > 0;
298 let b2 = bsdf.num_components(
299 BSDF_GLOSSY
300 | BSDF_REFLECTION
301 | BSDF_TRANSMISSION,
302 ) > 0;
303 (b1, b2)
304 };
305 if is_diffuse || (is_glossy && depth == max_depth - 1) {
306 let mut pixel = pixel.write().unwrap();
307 pixel.vp = SPPMVisiblePoint {
308 p: isect.p,
309 wo: wo,
310 bsdf: Some(bsdf.clone()),
311 beta: beta.clone(),
312 };
313 break;
314 }
315
316 // Spawn ray from SPPM camera path vertex
317 if depth < max_depth - 1 {
318 let bsdf = bsdf.as_ref();
319 if let Some((f, wi, pdf, t)) = bsdf.sample_f(
320 &wo,
321 &tile_sampler.get_2d(),
322 BSDF_ALL,
323 ) {
324 if pdf <= 0.0 || f.is_black() {
325 break;
326 }
327 specular_bounce = (t & BSDF_SPECULAR) != 0;
328 beta *= f
329 * (Vector3f::abs_dot(
330 &wi,
331 &isect.shading.n,
332 ) / pdf);
333
334 let beta_y = beta.y();
335 if beta_y < 0.25 {
336 let continue_prob = Float::min(1.0, beta_y);
337 if tile_sampler.get_1d() > continue_prob {
338 break;
339 }
340 beta /= continue_prob;
341 }
342 ray = isect.spawn_ray(&wi).into();
343 } else {
344 break;
345 }
346 }
347 } else {
348 ray = isect.spawn_ray(&ray.ray.d).into();
349 continue;
350 }
351 } else {
352 let mut pixel = pixel.write().unwrap();
353 for light in scene.lights.iter() {
354 let light = light.as_ref();
355 pixel.ld += beta * light.le(&ray);
356 }
357 break;
358 }
359 depth += 1;
360 }
361 }
362 }
363 }
364 });
365 }
366
367 {
368 let mut progress = progress.write().unwrap();
369 progress.update(1);
370 }
371
372 // Create grid of all SPPM visible points
373
374 // Compute grid bounds for SPPM visible points
375 let (grid_bounds, max_radius) = {
376 let pixels = pixels.read().unwrap();
377 let p0 = pixels.pixels[0].read().unwrap().vp.p;
378 let mut max_radius = pixels.pixels[0].read().unwrap().radius;
379 let mut min = p0;
380 let mut max = p0;
381 for i in 0..n_pixels {
382 let pixel = &pixels.pixels[i];
383 let pixel = pixel.read().unwrap();
384 if pixel.vp.beta.is_black() {
385 continue;
386 }
387 let radius = pixel.radius;
388 let p = pixel.vp.p;
389 for i in 0..3 {
390 min[i] = Float::min(min[i], p[i] - radius);
391 max[i] = Float::max(max[i], p[i] + radius);
392 }
393 max_radius = Float::max(max_radius, radius);
394 }
395 (Bounds3f::new(&min, &max), max_radius)
396 };
397 // Compute resolution of SPPM grid in each dimension
398 let diag = grid_bounds.diagonal();
399 let max_diag = max_component(&diag);
400 let base_grid_res = Float::floor(max_diag / max_radius);
401 let grid_res = [
402 Float::max(1.0, base_grid_res * diag[0] / max_diag) as i32,
403 Float::max(1.0, base_grid_res * diag[1] / max_diag) as i32,
404 Float::max(1.0, base_grid_res * diag[2] / max_diag) as i32,
405 ];
406
407 // Allocate grid for SPPM visible points
408 let hash_size = n_pixels;
409 let grid = Arc::new(RwLock::new(SPPMHashGrid {
410 nodes: vec![None; hash_size],
411 }));
412 {
413 let pixel_indices: Vec<usize> = (0..n_pixels).collect();
414 pixel_indices.par_iter().for_each(|pixel_index| {
415 let pixels = pixels.read().unwrap();
416 let pixel_ref = &pixels.pixels[*pixel_index];
417 let pixel = pixel_ref.read().unwrap();
418 if !pixel.vp.beta.is_black() {
419 // Add pixel's visible point to applicable grid cells
420 let p = pixel.vp.p;
421 let radius = pixel.radius;
422 let (_, pmin) = to_grid(
423 &(p - Vector3f::new(radius, radius, radius)),
424 &grid_bounds,
425 &grid_res,
426 );
427 let (_, pmax) = to_grid(
428 &(p + Vector3f::new(radius, radius, radius)),
429 &grid_bounds,
430 &grid_res,
431 );
432 {
433 let mut grid = grid.write().unwrap();
434 let grid = &mut grid.nodes;
435 for z in pmin.z..=pmax.z {
436 for y in pmin.y..=pmax.y {
437 for x in pmin.x..=pmax.x {
438 // Add visible point to grid cell $(x, y, z)$
439 let h = hash(&Point3i::new(x, y, z), hash_size);
440 {
441 let node = Arc::new(SPPMPixelListNode {
442 pixel: pixel_ref.clone(),
443 next: grid[h].clone(),
444 });
445 grid[h] = Some(node);
446 }
447 }
448 }
449 }
450
451 // Update statistics on grid cell sizes
452 {
453 let grid_cells_per_visible_point = (1 + pmax.x - pmin.x)
454 * (1 + pmax.y - pmin.y)
455 * (1 + pmax.z - pmin.z);
456 GRID_CELLS_PER_VISIBLE_POINT
457 .with(|c| c.add(grid_cells_per_visible_point as u64));
458 }
459 }
460 }
461 });
462 }
463
464 // Trace photons and accumulate contributions
465 {
466 let arena = Arc::new(RwLock::new(MemoryArena::new()));
467 let photons_per_iteration = self.photons_per_iteration as usize;
468 (0..photons_per_iteration)
469 .into_par_iter()
470 .for_each(|photon_index| {
471 let light_distr = light_distr.clone();
472 // Follow photon path for _photonIndex_
473 let halton_index =
474 iter as u64 * photons_per_iteration as u64 + photon_index as u64;
475
476 let mut halton_dim = 0;
477
478 // Choose light to shoot photon from
479 let light_sample = radical_inverse(halton_dim, halton_index);
480 let (light_num, light_pdf, _remapped) =
481 light_distr.sample_discrete(light_sample);
482 let light = scene.lights[light_num].clone();
483
484 // Compute sample values for photon from light
485 let u_light0 = Point2f::new(
486 radical_inverse(halton_dim, halton_index),
487 radical_inverse(halton_dim + 1, halton_index),
488 );
489 let u_light1 = Point2f::new(
490 radical_inverse(halton_dim + 2, halton_index),
491 radical_inverse(halton_dim + 3, halton_index),
492 );
493 let camera = camera.as_ref();
494 let shutter = camera.get_shutter();
495 let u_light_time = lerp(
496 radical_inverse(halton_dim + 4, halton_index),
497 shutter.0,
498 shutter.1,
499 );
500 halton_dim += 5;
501
502 // Generate _photonRay_ from light source and initialize _beta_
503 let light = light.as_ref();
504 if let Some((le, photon_ray, n_light, pdf_pos, pdf_dir)) =
505 light.sample_le(&u_light0, &u_light1, u_light_time)
506 {
507 if le.is_black() || pdf_pos == 0.0 || pdf_dir == 0.0 {
508 return;
509 }
510 let mut beta = (le * n_light.abs_dot(&photon_ray.d))
511 / (light_pdf * pdf_pos * pdf_dir);
512 if beta.is_black() {
513 return;
514 }
515
516 let mut photon_ray: RayDifferential = photon_ray.into();
517
518 // Follow photon path through scene and record intersections
519 let mut depth = 0;
520 while depth < max_depth {
521 if let Some(mut isect) = scene.intersect(&photon_ray.ray) {
522 if depth > 0 {
523 // Add photon contribution to nearby visible points
524 let (in_bounds, photon_grid_index) =
525 to_grid(&isect.p, &grid_bounds, &grid_res);
526 if in_bounds {
527 let h = hash(&photon_grid_index, hash_size);
528 // Add photon contribution to visible points in
529 // _grid[h]_
530 let mut node = {
531 let grid = grid.read().unwrap();
532 grid.nodes[h].clone()
533 };
534 while let Some(n) = node {
535 let pixel = n.pixel.as_ref();
536 {
537 let pixel = pixel.read().unwrap();
538 let radius = pixel.radius;
539 if Vector3f::distance_squared(
540 &pixel.vp.p,
541 &isect.p,
542 ) > (radius * radius)
543 {
544 node = n.next.clone();
545 continue;
546 }
547 }
548 {
549 let pixel = pixel.write().unwrap();
550 assert!(pixel.vp.bsdf.is_some());
551 }
552 // Update _pixel_ $\Phi$ and $M$ for nearby
553 // photon
554 let phi = {
555 let pixel = pixel.read().unwrap();
556 assert!(pixel.vp.bsdf.is_some());
557 let bsdf = pixel.vp.bsdf.as_ref().unwrap();
558 let wi = -photon_ray.ray.d;
559 beta * bsdf.f(&pixel.vp.wo, &wi, BSDF_ALL)
560 };
561 {
562 let mut pixel = pixel.write().unwrap();
563 pixel.phi += phi;
564 pixel.m += 1;
565 }
566 node = n.next.clone();
567 }
568 }
569 }
570 // Sample new photon ray direction
571
572 // Compute BSDF at photon intersection point
573 {
574 let arena = arena.clone();
575 let mut arena = arena.write().unwrap();
576 isect.compute_scattering_functions(
577 &photon_ray,
578 &mut arena,
579 TransportMode::Importance,
580 true,
581 );
582 }
583 if isect.bsdf.is_none() {
584 // Skip over medium boundaries for photon tracing
585 photon_ray = isect.spawn_ray(&photon_ray.ray.d).into();
586 continue;
587 }
588
589 if let Some(photon_bsdf) = isect.bsdf.as_ref() {
590 // Sample BSDF for photon scattering
591 let wo = -photon_ray.ray.d;
592 // Generate _bsdfSample_ for outgoing photon sample
593 let bsdf_sample = Point2f::new(
594 radical_inverse(halton_dim, halton_index),
595 radical_inverse(halton_dim + 1, halton_index),
596 );
597 halton_dim += 2;
598
599 let photon_bsdf = photon_bsdf.as_ref();
600 if let Some((fr, wi, pdf, _flags)) =
601 photon_bsdf.sample_f(&wo, &bsdf_sample, BSDF_ALL)
602 {
603 if fr.is_black() || pdf == 0.0 {
604 break;
605 }
606 //
607 let bnew = beta
608 * fr
609 * (Vector3f::abs_dot(&wi, &isect.shading.n) / pdf);
610
611 // Possibly terminate photon path with Russian roulette
612 let q = Float::max(0.0, 1.0 - bnew.y() / beta.y());
613 let t = radical_inverse(halton_dim, halton_index);
614 halton_dim += 1;
615 if t < q {
616 break;
617 }
618 beta = bnew / (1.0 - q);
619 photon_ray = isect.spawn_ray(&wi).into();
620 } else {
621 break;
622 }
623 } else {
624 break;
625 }
626 } else {
627 break;
628 }
629 depth += 1;
630 }
631 } else {
632 return;
633 }
634 });
635 PHOTON_PATHS.with(|c| c.add(photons_per_iteration as u64));
636 }
637
638 // Update pixel values from this pass's photons
639 {
640 (0..n_pixels).into_par_iter().for_each(|i| {
641 let pixels = pixels.clone();
642 let pixels = pixels.read().unwrap();
643 let pixel = &pixels.pixels[i];
644 let mut p = pixel.write().unwrap();
645 if p.m > 0 {
646 // Update pixel photon count, search radius, and $\tau$ from
647 // photons
648 let gamma = 2.0 / 3.0;
649 let nnew = p.n as Float + gamma * p.m as Float;
650 let rnew = p.radius * Float::sqrt(nnew / (p.n as Float + p.m as Float));
651 let phi = p.phi;
652
653 p.tau = (p.tau + p.vp.beta * phi) * ((rnew * rnew) / (p.radius * p.radius));
654 p.n = nnew;
655 p.radius = rnew;
656 p.m = 0;
657 p.phi = Spectrum::zero();
658 }
659 // Reset _VisiblePoint_ in _pixel_
660 p.vp.beta = Spectrum::zero();
661 p.vp.bsdf = None;
662 });
663 }
664
665 // Periodically store SPPM image in film and write image
666 {
667 let photons_per_iteration = self.photons_per_iteration as u32;
668 let write_frequency = self.write_frequency as u32;
669 let write_image = /*(iter + 1) == n_iterations || */((iter + 1) % write_frequency) == 0;
670 {
671 let x0 = pixel_bounds.min.x;
672 let x1 = pixel_bounds.max.x;
673 let y0 = pixel_bounds.min.y;
674 let y1 = pixel_bounds.max.y;
675 let np = (iter + 1) * photons_per_iteration;
676
677 let area = pixel_bounds.area() as usize;
678 assert_eq!(area, (x1 - x0) as usize * (y1 - y0) as usize);
679 let mut image = vec![Spectrum::default(); area];
680
681 let pixels = pixels.clone();
682 let pixels = pixels.read().unwrap();
683 let width = (x1 - x0) as usize;
684 let mut offset = 0;
685 for y in y0..y1 {
686 for x in x0..x1 {
687 // Compute radiance _L_ for SPPM pixel _pixel_
688 let yy = (y - pixel_bounds.min.y) as usize;
689 let pixel_index = yy * width + (x - x0) as usize;
690 let pixel = &pixels.pixels[pixel_index];
691 let pixel = pixel.read().unwrap();
692 let mut l = pixel.ld / ((iter + 1) as Float);
693 l += pixel.tau / (np as Float * PI * pixel.radius * pixel.radius);
694 image[offset] = l;
695 offset += 1;
696 }
697 }
698 {
699 let film = self.get_film();
700 let mut film = film.write().unwrap();
701 film.set_image(&image);
702 if write_image {
703 film.write_image();
704 }
705 film.update_display(&pixel_bounds);
706 }
707 }
708 }
709
710 {
711 let mut progress = progress.write().unwrap();
712 progress.update(1);
713 }
714 }
715
716 {
717 let mut progress = progress.write().unwrap();
718 progress.done();
719 }
720
721 {
722 let film = self.get_film();
723 let mut film = film.write().unwrap();
724 film.render_end();
725 film.write_image();
726 }
727 }
728
729 fn get_camera(&self) -> Arc<dyn Camera> {
730 Arc::clone(&self.camera)
731 }
732}
733
734pub fn create_sppm_integrator(
735 params: &ParamSet,
736 camera: &Arc<dyn Camera>,
737) -> Result<Arc<RwLock<dyn Integrator>>, PbrtError> {
738 let mut n_iterations =
739 params.find_one_int("iterations", params.find_one_int("numiterations", 64));
740 let max_depth = params.find_one_int("maxdepth", 5);
741 let photons_per_iteration = params.find_one_int("photonsperiteration", -1);
742 let write_frequency = params.find_one_int("imagewritefrequency", 1 << 31);
743 let initial_search_radius = params.find_one_float("radius", 1.0);
744 {
745 let options = PbrtOptions::get();
746 if options.quick_render {
747 n_iterations = (n_iterations / 16).max(1);
748 }
749 }
750 //if (PbrtOptions.quickRender) nIterations = std::max(1, nIterations / 16);
751 return Ok(Arc::new(RwLock::new(SPPMIntegrator::new(
752 Arc::clone(camera),
753 initial_search_radius,
754 n_iterations,
755 max_depth,
756 photons_per_iteration,
757 write_frequency,
758 ))));
759}