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}