Release Notes for v0.5.1
2019-02-19Documentation
I provide documentation for the latest official release and intermediate (between releases) updates. The main difference is that one links to source code (for example to the SPPMPixel struct), whereas the other does not, but is updated more frequently.
Stochastic Progressive Photon Mapping (SPPM)
The biggest changes for this release is that we support Stochastic Progressive Photon Mapping (SPPM) now (see also paper). Here an example scene (or a whole repository full of them):

The details about the progress and problems I ran into are documented in the related issue #86.
Lesson(s) Learned
One interesting detail mentioned in issue #86 above is that there
was a major bottleneck slowing down the executable and after
investigating a bit using perf I figured out that the
HaltonSampler was part of the problem. In C++ (the original code)
all instances of the class share a static vector<uint16_t>:
class HaltonSampler : public GlobalSampler {
...
  private:
    // HaltonSampler Private Data
    static std::vector<uint16_t> radicalInversePermutations;
...
};
In the Programming Rust book I found a solution for that problem. In chapter 17 there is a section talking about Building Regex Values Lazily, which mentions a crate called lazy_static. In chapter 19 there is a section about Global Variables, which mentions that static initializers (in Rust) can not call functions, but that the lazy_static crate can help to get around this problem. So the solution looks like this:
/// Generate random digit permutations for Halton sampler
lazy_static! {
    #[derive(Debug)]
    static ref RADICAL_INVERSE_PERMUTATIONS: Vec<u16> = {
        let mut rng: Rng = Rng::new();
        let radical_inverse_permutations: Vec<u16> = compute_radical_inverse_permutations(&mut rng);
        radical_inverse_permutations
    };
}
Defining a variable with the lazy_static! macro lets you use any
expression you like to initialize it; it runs the first time the
variable is dereferenced, and the value is saved for all subsequent
uses.
The problem with SPPM was that it creates a HaltonSampler once, but
clones it many times:
pub fn render_sppm(
    scene: &Scene,
    camera: &Arc<Camera + Send + Sync>,
    _sampler: &mut Box<Sampler + Send + Sync>,
    integrator: &mut Box<SPPMIntegrator>,
    num_threads: u8,
) {
...
        let mut sampler: Box<HaltonSampler> = Box::new(HaltonSampler::new(
            integrator.n_iterations as i64,
            pixel_bounds,
            false,
        ));
...
        for iteration in pbr::PbIter::new(0..integrator.n_iterations) {
...
                                    let mut tile_sampler = sampler.clone();
...
        }
...
}
New module, crates and structs
If you are interested in the details about new modules, structs, traits, and functions added by this release, here are some links to the documentation:
- 
The module sppm contains four structs: The SPPMIntegratorwhich basically stores the parameters described here for that particular integrator.SPPMPixelrecords (among other things) the weighted sum of emitted and reflected direct illumination for all camera path vertices for the pixel. It also contains information about aVisiblePointstructure, which records a point found along a camera path at which we’ll look for nearby photons during the photon shooting pass. Finally, theSPPMPixelListNodestruct is used to create a linked list within a grid (see details here).
- 
Beside the lazy_staticcrate mentioned abovers-pbrtuses now the atom crate. It provides theAtomandAtomSetOncestructs.
It is interesting how the grid first builds a vector of linked lists
by using Atom for the first entry, but AtomSetOnce for all linked
nodes:
pub struct SPPMPixelListNode<'p> {
    pub pixel: &'p SPPMPixel,
    pub next: AtomSetOnce<Arc<SPPMPixelListNode<'p>>>,
}
...
            let mut grid: Vec<Atom<Arc<SPPMPixelListNode>>> = Vec::with_capacity(hash_size);
...
                                        // add pixel's visible point to applicable grid cells
...
                                                    // add visible point to grid cell $(x, y, z)$
                                                    let h: usize = hash(
                                                        &Point3i { x: x, y: y, z: z },
                                                        hash_size as i32,
                                                    );
                                                    let mut node_arc =
                                                        Arc::new(SPPMPixelListNode::new(pixel));
                                                    let old_opt = grid[h].swap(node_arc.clone());
                                                    if let Some(old) = old_opt {
                                                        node_arc.next.set_if_none(old);
                                                    }
Later that grid gets replaced by a vector which replaces the first
Atom list entry by AtomSetOnce:
            let mut grid_once: Vec<AtomSetOnce<Arc<SPPMPixelListNode>>> =
                Vec::with_capacity(hash_size);
...
            for h in 0..hash_size {
                // take
                let opt = grid[h].take();
                if let Some(p) = opt {
                    grid_once[h].set_if_none(p);
                }
            }
...
                                                        if !grid_once[h].is_none() {
                                                            let mut opt = grid_once[h].get();
                                                            loop {
                                                                // deal with linked list
                                                                if let Some(node) = opt {
...
                                                                        // update opt
                                                                        opt = node.next.get();
...
                                                                        // update opt
                                                                        opt = node.next.get();
...
                                                            }
                                                        }
Minor changes
All cameras inplementing the Camera trait have to implement two
new methods now:
pub trait Camera {
...
    fn get_shutter_open(&self) -> Float;
    fn get_shutter_close(&self) -> Float;
...
}
The struct Film has two additional methods:
impl Film {
...
    pub fn get_cropped_pixel_bounds(&self) -> Bounds2i {
        self.cropped_pixel_bounds.clone()
    }
...
    pub fn set_image(&self, img: &[Spectrum]) {
...
    }
I converted core::integrator::uniform_sample_one_light() to a
generic function:
pub fn uniform_sample_one_light<S: Sampler + Send + Sync + ?Sized>(
    it: &SurfaceInteraction,
    scene: &Scene,
    sampler: &mut Box<S>,
    handle_media: bool,
    light_distrib: Option<&Distribution1D>,
) -> Spectrum {
...
}
This should make it easier to call that function for implementors of
the Sampler trait, e.g. with a pointer to a HaltonSampler.
The trait GlobalSampler demands a method now:
pub trait GlobalSampler: Sampler {
    fn set_sample_number(&mut self, sample_num: i64) -> bool;
}
That currently affects SobolSampler and HaltonSampler:
$ rg -trust set_sample_number -B 1 ~/git/github/rs_pbrt
/home/jan/git/github/rs_pbrt/src/samplers/sobol.rs
211-impl GlobalSampler for SobolSampler {
212:    fn set_sample_number(&mut self, sample_num: i64) -> bool {
/home/jan/git/github/rs_pbrt/src/samplers/halton.rs
300-impl GlobalSampler for HaltonSampler {
301:    fn set_sample_number(&mut self, sample_num: i64) -> bool {
/home/jan/git/github/rs_pbrt/src/core/sampler.rs
38-pub trait GlobalSampler: Sampler {
39:    fn set_sample_number(&mut self, sample_num: i64) -> bool;
/home/jan/git/github/rs_pbrt/src/integrators/sppm.rs
220-                                        tile_sampler.start_pixel(&p_pixel);
221:                                        tile_sampler.set_sample_number(iteration as i64);
The End
I hope I didn't forget anything important. Have fun and enjoy the v0.5.1 release.