use crate::core::filter::FilterTrait; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use crate::utils::Ptr; use crate::utils::containers::DeviceArray2D; use crate::utils::math::{ BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, }; use crate::utils::rng::Rng; use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::{hash::*, sobol}; use enum_dispatch::enum_dispatch; #[repr(C)] #[derive(Debug, Default, Clone, Copy)] pub struct CameraSample { pub p_film: Point2f, pub p_lens: Point2f, pub time: Float, pub filter_weight: Float, } pub fn get_camera_sample(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample where S: SamplerTrait, F: FilterTrait, { let fs = filter.sample(sampler.get_pixel2d()); CameraSample { p_film: Point2f::from(p_pixel) + Vector2f::from(fs.p) + Vector2f::new(0.5, 0.5), time: sampler.get1d(), p_lens: sampler.get2d(), filter_weight: fs.weight, } } #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct IndependentSampler { pub samples_per_pixel: i32, pub seed: u64, pub rng: Rng, } impl IndependentSampler { pub fn new(samples_per_pixel: i32, seed: u64) -> Self { Self { samples_per_pixel, seed, rng: Rng::default(), } } } impl SamplerTrait for IndependentSampler { fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let sequence_index = hash_buffer(&hash_input, 0); self.rng.set_sequence(sequence_index); self.rng .advance((sample_index as u64) * 65536 + (dim.unwrap_or(0) as u64)); } fn get1d(&mut self) -> Float { self.rng.uniform::() } fn get2d(&mut self) -> Point2f { Point2f::new(self.rng.uniform::(), self.rng.uniform::()) } fn get_pixel2d(&mut self) -> Point2f { self.get2d() } } pub const MAX_HALTON_RESOLUTION: i32 = 128; #[repr(C)] #[derive(Debug, Default, Clone, PartialEq, Eq, Copy)] pub enum RandomizeStrategy { #[default] None, PermuteDigits, FastOwen, Owen, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct HaltonSampler { pub samples_per_pixel: i32, pub randomize: RandomizeStrategy, pub base_scales: [u64; 2], pub base_exponents: [u64; 2], pub mult_inverse: [u64; 2], pub halton_index: u64, pub dim: u32, pub digit_permutations: Ptr, } #[allow(clippy::derivable_impls)] impl Default for HaltonSampler { fn default() -> Self { Self { samples_per_pixel: 0, randomize: RandomizeStrategy::default(), base_scales: [0; 2], base_exponents: [0; 2], mult_inverse: [0; 2], halton_index: 0, dim: 0, digit_permutations: Ptr::default(), } } } impl HaltonSampler { pub fn sample_dimension(&self, dimension: u32) -> Float { if self.randomize == RandomizeStrategy::None { radical_inverse(dimension, self.halton_index) } else if self.randomize == RandomizeStrategy::PermuteDigits { let digit_perm = unsafe { &*self.digit_permutations.add(dimension as usize) }; scrambled_radical_inverse(dimension, self.halton_index, digit_perm) } else { owen_scrambled_radical_inverse( dimension, self.halton_index, mix_bits(1 + ((dimension as u64) << 4)) as u32, ) } } pub fn multiplicative_inverse(a: i64, n: i64) -> u64 { let (x, _) = Self::extended_gcd(a as u64, n as u64); x.rem_euclid(n) as u64 } pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) { if b == 0 { return (1, 0); } let (xp, yp) = Self::extended_gcd(b, a % b); let d = (a / b) as i64; (yp, xp - d * yp) } } impl SamplerTrait for HaltonSampler { fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.halton_index = 0; let sample_stride = self.base_scales[0] * self.base_scales[1]; if sample_stride > 1 { let pm_x = p.x().rem_euclid(MAX_HALTON_RESOLUTION) as u64; let pm_y = p.y().rem_euclid(MAX_HALTON_RESOLUTION) as u64; let dim_offset_x = inverse_radical_inverse(pm_x, 2, self.base_exponents[0]); self.halton_index = self.halton_index.wrapping_add( dim_offset_x .wrapping_mul(sample_stride / self.base_scales[0]) .wrapping_mul(self.mult_inverse[0]), ); let dim_offset_y = inverse_radical_inverse(pm_y, 3, self.base_exponents[1]); self.halton_index = self.halton_index.wrapping_add( dim_offset_y .wrapping_mul(sample_stride / self.base_scales[1]) .wrapping_mul(self.mult_inverse[1]), ); self.halton_index %= sample_stride; } self.halton_index = self .halton_index .wrapping_add((sample_index as u64).wrapping_mul(sample_stride)); self.dim = 2.max(dim.unwrap_or(0)); } fn get1d(&mut self) -> Float { if self.dim > PRIME_TABLE_SIZE as u32 { self.dim = 2; } self.sample_dimension(self.dim) } fn get2d(&mut self) -> Point2f { if self.dim > PRIME_TABLE_SIZE as u32 { self.dim = 2; } let dim = self.dim; self.dim += 2; Point2f::new(self.sample_dimension(dim), self.sample_dimension(dim + 1)) } fn get_pixel2d(&mut self) -> Point2f { Point2f::new( radical_inverse(0, self.halton_index >> self.base_exponents[0]), radical_inverse(1, self.halton_index >> self.base_exponents[1]), ) } } #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct StratifiedSampler { x_pixel_samples: i32, y_pixel_samples: i32, jitter: bool, seed: u64, rng: Rng, pixel: Point2i, sample_index: i32, dim: u32, } impl StratifiedSampler { pub fn new( x_pixel_samples: i32, y_pixel_samples: i32, seed: Option, jitter: bool, ) -> Self { Self { x_pixel_samples, y_pixel_samples, jitter, seed: seed.unwrap_or(0), rng: Rng::default(), pixel: Point2i::default(), sample_index: 0, dim: 0, } } } impl SamplerTrait for StratifiedSampler { fn samples_per_pixel(&self) -> i32 { self.x_pixel_samples * self.y_pixel_samples } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.sample_index = sample_index; let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let sequence_index = hash_buffer(&hash_input, 0); self.rng.set_sequence(sequence_index); self.rng .advance((sample_index as u64) * 65536 + (dim.unwrap_or(0) as u64)); } fn get1d(&mut self) -> Float { let hash_input = [ self.pixel.x() as u64, self.pixel.y() as u64, self.dim as u64, self.seed, ]; let hash = hash_buffer(&hash_input, 0); let stratum = permutation_element( self.sample_index as u32, self.samples_per_pixel() as u32, hash as u32, ); self.dim += 1; let delta = if self.jitter { self.rng.uniform::() } else { 0.5 }; (stratum as Float + delta) / (self.samples_per_pixel() as Float) } fn get2d(&mut self) -> Point2f { let hash_input = [ self.pixel.x() as u64, self.pixel.y() as u64, self.dim as u64, self.seed, ]; let hash = hash_buffer(&hash_input, 0); let stratum = permutation_element( self.sample_index as u32, self.samples_per_pixel() as u32, hash as u32, ); self.dim += 2; let x = stratum % self.x_pixel_samples as u32; let y = stratum / self.y_pixel_samples as u32; let dx = if self.jitter { self.rng.uniform::() } else { 0.5 }; let dy = if self.jitter { self.rng.uniform::() } else { 0.5 }; Point2f::new( (x as Float + dx) / self.x_pixel_samples as Float, (y as Float + dy) / self.y_pixel_samples as Float, ) } fn get_pixel2d(&mut self) -> Point2f { self.get2d() } } #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct PaddedSobolSampler { samples_per_pixel: i32, seed: u64, randomize: RandomizeStrategy, pixel: Point2i, sample_index: i32, dim: u32, } impl PaddedSobolSampler { pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option) -> Self { Self { samples_per_pixel, seed: seed.unwrap_or(0), randomize, pixel: Point2i::default(), sample_index: 0, dim: 0, } } fn sample_dimension(&self, dimension: u32, a: u32, hash: u32) -> Float { if self.randomize == RandomizeStrategy::None { return sobol_sample(a as u64, dimension, NoRandomizer); } match self.randomize { RandomizeStrategy::PermuteDigits => { sobol_sample(a as u64, dimension, BinaryPermuteScrambler::new(hash)) } RandomizeStrategy::FastOwen => { sobol_sample(a as u64, dimension, FastOwenScrambler::new(hash)) } RandomizeStrategy::Owen => sobol_sample(a as u64, dimension, OwenScrambler::new(hash)), RandomizeStrategy::None => unreachable!(), } } } impl SamplerTrait for PaddedSobolSampler { fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.sample_index = sample_index; self.dim = dim.unwrap_or(0); } fn get1d(&mut self) -> Float { let hash_input = [ self.pixel.x() as u64, self.pixel.y() as u64, self.dim as u64, self.seed, ]; let hash = hash_buffer(&hash_input, 0); let index = permutation_element( self.sample_index as u32, self.samples_per_pixel as u32, hash as u32, ); self.sample_dimension(0, index, (hash >> 32) as u32) } fn get2d(&mut self) -> Point2f { let hash_input = [ self.pixel.x() as u64, self.pixel.y() as u64, self.dim as u64, self.seed, ]; let hash = hash_buffer(&hash_input, 0); let index = permutation_element( self.sample_index as u32, self.samples_per_pixel as u32, hash as u32, ); self.dim += 2; Point2f::new( self.sample_dimension(0, index, hash as u32), self.sample_dimension(1, index, (hash >> 32) as u32), ) } fn get_pixel2d(&mut self) -> Point2f { self.get2d() } } #[derive(Default, Debug, Clone)] pub struct SobolSampler { samples_per_pixel: i32, scale: i32, seed: u64, randomize: RandomizeStrategy, pixel: Point2i, dim: u32, sobol_index: u64, } impl SobolSampler { pub fn new( samples_per_pixel: i32, full_resolution: Point2i, randomize: RandomizeStrategy, seed: Option, ) -> Self { let scale = round_up_pow2(full_resolution.x().max(full_resolution.y())); Self { samples_per_pixel, scale, seed: seed.unwrap_or(0), randomize, pixel: Point2i::default(), dim: 0, sobol_index: 0, } } fn sample_dimension(&self, dimension: u32) -> Float { if self.randomize == RandomizeStrategy::None { return sobol_sample(self.sobol_index, dimension, NoRandomizer); } let hash_input = [self.pixel.x() as u64, self.pixel.y() as u64, self.seed]; let hash = hash_buffer(&hash_input, 0) as u32; match self.randomize { RandomizeStrategy::PermuteDigits => sobol_sample( self.sobol_index, dimension, BinaryPermuteScrambler::new(hash), ), RandomizeStrategy::FastOwen => { sobol_sample(self.sobol_index, dimension, FastOwenScrambler::new(hash)) } RandomizeStrategy::Owen => { sobol_sample(self.sobol_index, dimension, OwenScrambler::new(hash)) } RandomizeStrategy::None => unreachable!(), } } } impl SamplerTrait for SobolSampler { fn samples_per_pixel(&self) -> i32 { self.samples_per_pixel } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.pixel = p; self.dim = 2.max(dim.unwrap_or(0)); self.sobol_index = sobol_interval_to_index(log2_int(self.scale as Float) as u32, sample_index as u64, p) } fn get1d(&mut self) -> Float { if self.dim >= N_SOBOL_DIMENSIONS as u32 { self.dim = 2; } let dim = self.dim; self.dim += 1; self.sample_dimension(dim) } fn get2d(&mut self) -> Point2f { if self.dim >= N_SOBOL_DIMENSIONS as u32 { self.dim = 2; } let u = Point2f::new( self.sample_dimension(self.dim), self.sample_dimension(self.dim + 1), ); self.dim += 2; u } fn get_pixel2d(&mut self) -> Point2f { let mut u = Point2f::new( sobol_sample(self.sobol_index, 0, NoRandomizer), sobol_sample(self.sobol_index, 1, NoRandomizer), ); u[0] = clamp( u[0] * self.scale as Float - self.pixel[0] as Float, 0., ONE_MINUS_EPSILON, ) as Float; u[1] = clamp( u[1] * self.scale as Float - self.pixel[1] as Float, 1., ONE_MINUS_EPSILON, ) as Float; u } } #[repr(C)] #[derive(Default, Copy, Debug, Clone)] pub struct ZSobolSampler { randomize: RandomizeStrategy, seed: u64, log2_samples_per_pixel: i32, n_base4_digits: u32, morton_index: u64, dim: u32, } impl ZSobolSampler { pub fn new( samples_per_pixel: i32, full_resolution: Point2i, randomize: RandomizeStrategy, seed: Option, ) -> Self { let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32; let res = round_up_pow2(full_resolution.x().max(full_resolution.y())); let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2); let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel as u32; Self { randomize, seed: seed.unwrap_or(0), log2_samples_per_pixel: log2_samples_per_pixel as i32, n_base4_digits, morton_index: 0, dim: 0, } } fn get_sample_index(&self) -> u64 { const PERMUTATIONS: [[u8; 4]; 24] = [ [0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1], [0, 3, 2, 1], [0, 3, 1, 2], [1, 0, 2, 3], [1, 0, 3, 2], [1, 2, 0, 3], [1, 2, 3, 0], [1, 3, 2, 0], [1, 3, 0, 2], [2, 1, 0, 3], [2, 1, 3, 0], [2, 0, 1, 3], [2, 0, 3, 1], [2, 3, 0, 1], [2, 3, 1, 0], [3, 1, 2, 0], [3, 1, 0, 2], [3, 2, 1, 0], [3, 2, 0, 1], [3, 0, 2, 1], [3, 0, 1, 2], ]; let mut sample_index = 0; let pow2_samples = (self.log2_samples_per_pixel & 1) != 0; let last_digit = if pow2_samples { 1 } else { 0 }; for i in (last_digit..self.n_base4_digits).rev() { let digit_shift = (2 * i) - if pow2_samples { 1 } else { 0 }; let mut digit = (self.morton_index >> digit_shift) & 3; let higher_digits = self.morton_index >> (digit_shift + 2); let mix_input = higher_digits ^ (0x55555555 * self.dim as u64); let p = (mix_bits(mix_input) >> 24) % 24; digit = PERMUTATIONS[p as usize][digit as usize] as u64; sample_index |= digit << digit_shift; } if pow2_samples { let lsb = self.morton_index & 1; sample_index |= lsb; } sample_index } } impl SamplerTrait for ZSobolSampler { fn samples_per_pixel(&self) -> i32 { todo!() } fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option) { self.dim = dim.unwrap_or(0); self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) << self.log2_samples_per_pixel) | sample_index as u64 } fn get1d(&mut self) -> Float { let sample_index = self.get_sample_index(); let hash_input = [self.dim as u64, self.seed]; let hash = hash_buffer(&hash_input, 0) as u32; self.dim += 1; if self.randomize == RandomizeStrategy::None { return sobol_sample(sample_index, self.dim, NoRandomizer); } match self.randomize { RandomizeStrategy::PermuteDigits => { sobol_sample(sample_index, self.dim, BinaryPermuteScrambler::new(hash)) } RandomizeStrategy::FastOwen => { sobol_sample(sample_index, self.dim, FastOwenScrambler::new(hash)) } RandomizeStrategy::Owen => { sobol_sample(sample_index, self.dim, OwenScrambler::new(hash)) } RandomizeStrategy::None => unreachable!(), } } fn get2d(&mut self) -> Point2f { let sample_index = self.get_sample_index(); self.dim += 2; let hash_input = [self.dim as u64, self.seed]; let hash = hash_buffer(&hash_input, 0); let sample_hash = [hash as u32, (hash >> 32) as u32]; if self.randomize == RandomizeStrategy::None { return Point2f::new( sobol_sample(sample_index, 0, NoRandomizer), sobol_sample(sample_index, 1, NoRandomizer), ); } match self.randomize { RandomizeStrategy::PermuteDigits => Point2f::new( sobol_sample(sample_index, 0, BinaryPermuteScrambler::new(sample_hash[0])), sobol_sample(sample_index, 1, BinaryPermuteScrambler::new(sample_hash[1])), ), RandomizeStrategy::FastOwen => Point2f::new( sobol_sample(sample_index, 0, FastOwenScrambler::new(sample_hash[0])), sobol_sample(sample_index, 1, FastOwenScrambler::new(sample_hash[1])), ), RandomizeStrategy::Owen => Point2f::new( sobol_sample(sample_index, 0, OwenScrambler::new(sample_hash[0])), sobol_sample(sample_index, 1, OwenScrambler::new(sample_hash[1])), ), RandomizeStrategy::None => unreachable!(), } } fn get_pixel2d(&mut self) -> Point2f { self.get2d() } } #[derive(Default, Debug, Clone)] pub struct MLTSampler; impl SamplerTrait for MLTSampler { fn samples_per_pixel(&self) -> i32 { todo!() } fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: i32, _dim: Option) { todo!() } fn get1d(&mut self) -> Float { todo!() } fn get2d(&mut self) -> Point2f { todo!() } fn get_pixel2d(&mut self) -> Point2f { todo!() } } #[enum_dispatch] pub trait SamplerTrait { fn samples_per_pixel(&self) -> i32; fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option); fn get1d(&mut self) -> Float; fn get2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f; } #[enum_dispatch(SamplerTrait)] #[derive(Debug, Clone)] pub enum Sampler { Independent(IndependentSampler), Stratified(StratifiedSampler), Halton(HaltonSampler), PaddedSobol(PaddedSobolSampler), Sobol(SobolSampler), ZSobol(ZSobolSampler), MLT(MLTSampler), }